Skip to content

Commit 399c029

Browse files
committed
Don't open symbols into a module when evaluating canImport statements
The module loaders now have API to check whether a given module can be imported without importing the referenced module. This provides a significant speed boost to condition resolution and no longer introduces symbols from the referenced module into the current context without the user explicitly requesting it. The definition of ‘canImport’ does not necessarily mean that a full import without error is possible, merely that the path to the import is visible to the compiler and the module is loadable in some form or another. Note that this means this check is insufficient to guarantee that you are on one platform or another. For those kinds of checks, use ‘os(OSNAME)’.
1 parent 7583c10 commit 399c029

File tree

14 files changed

+182
-12
lines changed

14 files changed

+182
-12
lines changed

include/swift/AST/ASTContext.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,13 @@ class ASTContext {
580580
/// Does nothing in non-asserts (NDEBUG) builds.
581581
void verifyAllLoadedModules() const;
582582

583+
/// \brief Check whether the module with a given name can be imported without
584+
/// importing it.
585+
///
586+
/// Note that even if this check succeeds, errors may still occur if the
587+
/// module is loaded in full.
588+
bool canImportModule(std::pair<Identifier, SourceLoc> ModulePath);
589+
583590
/// \returns a module with a given name that was already loaded. If the
584591
/// module was not loaded, returns nullptr.
585592
ModuleDecl *getLoadedModule(

include/swift/AST/ModuleLoader.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ class ModuleLoader {
7474
public:
7575
virtual ~ModuleLoader() = default;
7676

77+
/// \brief Check whether the module with a given name can be imported without
78+
/// importing it.
79+
///
80+
/// Note that even if this check succeeds, errors may still occur if the
81+
/// module is loaded in full.
82+
virtual bool canImportModule(std::pair<Identifier, SourceLoc> named) = 0;
83+
7784
/// \brief Import a module with the given module path.
7885
///
7986
/// \param importLoc The location of the 'import' keyword.

include/swift/ClangImporter/ClangImporter.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ class ClangImporter final : public ClangModuleLoader {
9494

9595
~ClangImporter();
9696

97+
/// \brief Check whether the module with a given name can be imported without
98+
/// importing it.
99+
///
100+
/// Note that even if this check succeeds, errors may still occur if the
101+
/// module is loaded in full.
102+
virtual bool canImportModule(std::pair<Identifier, SourceLoc> named) override;
103+
97104
/// \brief Import a module with the given module path.
98105
///
99106
/// Clang modules will be imported using the Objective-C ARC dialect,

include/swift/Sema/SourceLoader.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ class SourceLoader : public ModuleLoader {
4848
SourceLoader &operator=(const SourceLoader &) = delete;
4949
SourceLoader &operator=(SourceLoader &&) = delete;
5050

51+
/// \brief Check whether the module with a given name can be imported without
52+
/// importing it.
53+
///
54+
/// Note that even if this check succeeds, errors may still occur if the
55+
/// module is loaded in full.
56+
virtual bool canImportModule(std::pair<Identifier, SourceLoc> named) override;
57+
5158
/// \brief Import a module with the given module path.
5259
///
5360
/// \param importLoc The location of the 'import' keyword.

include/swift/Serialization/SerializedModuleLoader.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ class SerializedModuleLoader : public ModuleLoader {
4848
SerializedModuleLoader &operator=(const SerializedModuleLoader &) = delete;
4949
SerializedModuleLoader &operator=(SerializedModuleLoader &&) = delete;
5050

51+
/// \brief Check whether the module with a given name can be imported without
52+
/// importing it.
53+
///
54+
/// Note that even if this check succeeds, errors may still occur if the
55+
/// module is loaded in full.
56+
virtual bool canImportModule(std::pair<Identifier, SourceLoc> named) override;
57+
5158
/// \brief Import a module with the given module path.
5259
///
5360
/// \param importLoc The location of the 'import' keyword.

lib/AST/ASTContext.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,6 +1279,20 @@ GenericEnvironment *ASTContext::getOrCreateCanonicalGenericEnvironment(
12791279
return env;
12801280
}
12811281

1282+
bool ASTContext::canImportModule(std::pair<Identifier, SourceLoc> ModulePath) {
1283+
// If this module has already been successfully imported, it is importable.
1284+
if (getLoadedModule(ModulePath) != nullptr)
1285+
return true;
1286+
1287+
// Otherwise, ask the module loaders.
1288+
for (auto &importer : Impl.ModuleLoaders) {
1289+
if (importer->canImportModule(ModulePath)) {
1290+
return true;
1291+
}
1292+
}
1293+
return false;
1294+
}
1295+
12821296
Module *
12831297
ASTContext::getModule(ArrayRef<std::pair<Identifier, SourceLoc>> ModulePath) {
12841298
assert(!ModulePath.empty());

lib/ClangImporter/ClangImporter.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,23 @@ bool ClangImporter::isModuleImported(const clang::Module *M) {
10221022
return M->NameVisibility == clang::Module::NameVisibilityKind::AllVisible;
10231023
}
10241024

1025+
bool ClangImporter::canImportModule(std::pair<Identifier, SourceLoc> moduleID) {
1026+
// Look up the top-level module to see if it exists.
1027+
// FIXME: This only works with top-level modules.
1028+
auto &clangHeaderSearch = Impl.getClangPreprocessor().getHeaderSearchInfo();
1029+
clang::Module *clangModule =
1030+
clangHeaderSearch.lookupModule(moduleID.first.str());
1031+
if (!clangModule) {
1032+
return false;
1033+
}
1034+
1035+
clang::Module::Requirement r;
1036+
clang::Module::UnresolvedHeaderDirective mh;
1037+
clang::Module *m;
1038+
auto &ctx = Impl.getClangASTContext();
1039+
return clangModule->isAvailable(ctx.getLangOpts(), getTargetInfo(), r, mh, m);
1040+
}
1041+
10251042
Module *ClangImporter::loadModule(
10261043
SourceLoc importLoc,
10271044
ArrayRef<std::pair<Identifier, SourceLoc>> path) {

lib/Parse/ParseStmt.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1790,8 +1790,7 @@ Parser::classifyConditionalCompilationExpr(Expr *condition,
17901790
return None;
17911791
}
17921792

1793-
auto *M = Context.getModule({ { argumentIdent, UDRE->getLoc() } });
1794-
return (M && !M->failedToLoad());
1793+
return Context.canImportModule({ argumentIdent, UDRE->getLoc() });
17951794
}
17961795

17971796
if (!fullCheck) {

lib/Sema/SourceLoader.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,22 @@ class SkipNonTransparentFunctions : public DelayedParsingCallbacks {
7070

7171
} // unnamed namespace
7272

73+
bool SourceLoader::canImportModule(std::pair<Identifier, SourceLoc> ID) {
74+
// Search the memory buffers to see if we can find this file on disk.
75+
FileOrError inputFileOrError = findModule(Ctx, ID.first.str(),
76+
ID.second);
77+
if (!inputFileOrError) {
78+
auto err = inputFileOrError.getError();
79+
if (err != std::errc::no_such_file_or_directory) {
80+
Ctx.Diags.diagnose(ID.second, diag::sema_opening_import,
81+
ID.first, err.message());
82+
}
83+
84+
return false;
85+
}
86+
return true;
87+
}
88+
7389
Module *SourceLoader::loadModule(SourceLoc importLoc,
7490
ArrayRef<std::pair<Identifier, SourceLoc>> path) {
7591
// FIXME: Swift submodules?

lib/Serialization/SerializedModuleLoader.cpp

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "swift/Basic/SourceManager.h"
2020
#include "swift/Basic/Version.h"
2121
#include "llvm/ADT/SmallString.h"
22+
#include "llvm/Support/FileSystem.h"
2223
#include "llvm/Support/MemoryBuffer.h"
2324
#include "llvm/Support/Path.h"
2425
#include "llvm/Support/Debug.h"
@@ -39,15 +40,25 @@ SerializedModuleLoader::~SerializedModuleLoader() = default;
3940
static std::error_code
4041
openModuleFiles(StringRef DirName, StringRef ModuleFilename,
4142
StringRef ModuleDocFilename,
42-
std::unique_ptr<llvm::MemoryBuffer> &ModuleBuffer,
43-
std::unique_ptr<llvm::MemoryBuffer> &ModuleDocBuffer,
43+
std::unique_ptr<llvm::MemoryBuffer> *ModuleBuffer,
44+
std::unique_ptr<llvm::MemoryBuffer> *ModuleDocBuffer,
4445
llvm::SmallVectorImpl<char> &Scratch) {
46+
assert(((ModuleBuffer && ModuleDocBuffer)
47+
|| (!ModuleBuffer && !ModuleDocBuffer))
48+
&& "Module and Module Doc buffer must both be initialized or NULL");
4549
// Try to open the module file first. If we fail, don't even look for the
4650
// module documentation file.
4751
Scratch.clear();
4852
llvm::sys::path::append(Scratch, DirName, ModuleFilename);
53+
// If there are no buffers to load into, simply check for the existence of
54+
// the module file.
55+
if (!(ModuleBuffer || ModuleDocBuffer)) {
56+
return llvm::sys::fs::access(StringRef(Scratch.data(), Scratch.size()),
57+
llvm::sys::fs::AccessMode::Exist);
58+
}
59+
4960
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> ModuleOrErr =
50-
llvm::MemoryBuffer::getFile(StringRef(Scratch.data(), Scratch.size()));
61+
llvm::MemoryBuffer::getFile(StringRef(Scratch.data(), Scratch.size()));
5162
if (!ModuleOrErr)
5263
return ModuleOrErr.getError();
5364

@@ -56,21 +67,23 @@ openModuleFiles(StringRef DirName, StringRef ModuleFilename,
5667
Scratch.clear();
5768
llvm::sys::path::append(Scratch, DirName, ModuleDocFilename);
5869
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> ModuleDocOrErr =
59-
llvm::MemoryBuffer::getFile(StringRef(Scratch.data(), Scratch.size()));
70+
llvm::MemoryBuffer::getFile(StringRef(Scratch.data(), Scratch.size()));
6071
if (!ModuleDocOrErr &&
6172
ModuleDocOrErr.getError() != std::errc::no_such_file_or_directory) {
6273
return ModuleDocOrErr.getError();
6374
}
64-
ModuleBuffer = std::move(ModuleOrErr.get());
75+
76+
*ModuleBuffer = std::move(ModuleOrErr.get());
6577
if (ModuleDocOrErr)
66-
ModuleDocBuffer = std::move(ModuleDocOrErr.get());
78+
*ModuleDocBuffer = std::move(ModuleDocOrErr.get());
79+
6780
return std::error_code();
6881
}
6982

7083
static bool
7184
findModule(ASTContext &ctx, AccessPathElem moduleID,
72-
std::unique_ptr<llvm::MemoryBuffer> &moduleBuffer,
73-
std::unique_ptr<llvm::MemoryBuffer> &moduleDocBuffer,
85+
std::unique_ptr<llvm::MemoryBuffer> *moduleBuffer,
86+
std::unique_ptr<llvm::MemoryBuffer> *moduleDocBuffer,
7487
bool &isFramework) {
7588
llvm::SmallString<64> moduleFilename(moduleID.first.str());
7689
moduleFilename += '.';
@@ -95,7 +108,6 @@ findModule(ASTContext &ctx, AccessPathElem moduleID,
95108

96109
llvm::SmallString<128> scratch;
97110
llvm::SmallString<128> currPath;
98-
99111
isFramework = false;
100112
for (auto path : ctx.SearchPathOpts.ImportSearchPaths) {
101113
auto err = openModuleFiles(path,
@@ -353,6 +365,21 @@ FileUnit *SerializedModuleLoader::loadAST(
353365
return nullptr;
354366
}
355367

368+
bool
369+
SerializedModuleLoader::canImportModule(std::pair<Identifier, SourceLoc> mID) {
370+
// First see if we find it in the registered memory buffers.
371+
if (!MemoryBuffers.empty()) {
372+
auto bufIter = MemoryBuffers.find(mID.first.str());
373+
if (bufIter != MemoryBuffers.end()) {
374+
return true;
375+
}
376+
}
377+
378+
// Otherwise look on disk.
379+
bool isFramework = false;
380+
return findModule(Ctx, mID, nullptr, nullptr, isFramework);
381+
}
382+
356383
Module *SerializedModuleLoader::loadModule(SourceLoc importLoc,
357384
Module::AccessPathTy path) {
358385
// FIXME: Swift submodules?
@@ -378,7 +405,7 @@ Module *SerializedModuleLoader::loadModule(SourceLoc importLoc,
378405

379406
// Otherwise look on disk.
380407
if (!moduleInputBuffer) {
381-
if (!findModule(Ctx, moduleID, moduleInputBuffer, moduleDocInputBuffer,
408+
if (!findModule(Ctx, moduleID, &moduleInputBuffer, &moduleDocInputBuffer,
382409
isFramework)) {
383410
return nullptr;
384411
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module MissingRequirement {
2+
requires missing
3+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// RUN: rm -rf %t && mkdir -p %t
2+
// RUN: cp -R %S/Inputs/mixed-target %t
3+
4+
// FIXME: BEGIN -enable-source-import hackaround
5+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-module -o %t %clang-importer-sdk-path/swift-modules/CoreGraphics.swift
6+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-module -o %t %clang-importer-sdk-path/swift-modules/Foundation.swift
7+
// FIXME: END -enable-source-import hackaround
8+
9+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk-nosource -I %t) -I %S/../Inputs/custom-modules -import-objc-header %t/mixed-target/header.h -emit-module-path %t/MixedWithHeader.swiftmodule %S/Inputs/mixed-with-header.swift %S/../../Inputs/empty.swift -module-name MixedWithHeader -disable-objc-attr-requires-foundation-module
10+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk-nosource -I %t) -I %S/../Inputs/custom-modules -typecheck %s -verify
11+
12+
// RUN: rm -rf %t/mixed-target/
13+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk-nosource -I %t) -I %S/../Inputs/custom-modules -typecheck %s -verify
14+
15+
// REQUIRES: objc_interop
16+
17+
// Test that 'canImport(Foo)' directives do not open symbols from 'Foo' into the
18+
// current module. Only an 'import Foo' statement should do this.
19+
20+
#if canImport(AppKit)
21+
class AppKitView : NSView {} // expected-error {{use of undeclared type 'NSView'}}
22+
#endif
23+
24+
#if canImport(UIKit)
25+
class UIKitView : UIView {} // expected-error {{use of undeclared type 'UIView'}}
26+
#endif
27+
28+
#if canImport(CoreGraphics)
29+
let square = CGRect(x: 100, y: 100, width: 100, height: 100) // expected-error {{use of unresolved identifier 'CGRect'}}
30+
// expected-error@-1 {{'square' used within its own type}}
31+
// expected-error@-2 {{could not infer type for 'square'}}
32+
let (r, s) = square.divided(atDistance: 50, from: .minXEdge)
33+
#endif
34+
35+
#if canImport(MixedWithHeader)
36+
let object = NSObject() // expected-error {{use of unresolved identifier 'NSObject'}}
37+
// expected-error@-1 {{'object' used within its own type}}
38+
// expected-error@-2 {{could not infer type for 'object'}}
39+
let someAPI = Derived() // expected-error {{use of unresolved identifier 'Derived'}}
40+
#endif
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -I %S/Inputs/missing-requirement %s -verify
2+
3+
// REQUIRES: objc_interop
4+
5+
class Unique {}
6+
7+
#if canImport(MissingRequirement)
8+
class Unique {} // No diagnostic
9+
#endif
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// RUN: rm -rf %t
2+
// RUN: mkdir -p %t
3+
//
4+
// RUN: echo "public var dummyVar = Int()" | %target-swift-frontend -module-name DummyModule -emit-module -o %t -
5+
// RUN: %target-swift-frontend -typecheck -verify -I %t %s
6+
7+
#if canImport(DummyModule)
8+
print(DummyModule.dummyVar) // expected-error {{use of unresolved identifier 'DummyModule'}}
9+
print(dummyVar) // expected-error {{use of unresolved identifier 'dummyVar'}}
10+
#endif

0 commit comments

Comments
 (0)