diff --git a/include/swift/AST/ASTContext.h b/include/swift/AST/ASTContext.h index b9837abf1ddfe..aed613d8c73c5 100644 --- a/include/swift/AST/ASTContext.h +++ b/include/swift/AST/ASTContext.h @@ -580,6 +580,13 @@ class ASTContext { /// Does nothing in non-asserts (NDEBUG) builds. void verifyAllLoadedModules() const; + /// \brief Check whether the module with a given name can be imported without + /// importing it. + /// + /// Note that even if this check succeeds, errors may still occur if the + /// module is loaded in full. + bool canImportModule(std::pair ModulePath); + /// \returns a module with a given name that was already loaded. If the /// module was not loaded, returns nullptr. ModuleDecl *getLoadedModule( diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 7157c363facd3..5caf3902c9b5a 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -578,6 +578,20 @@ class alignas(1 << DeclAlignInBits) Decl { enum { NumExtensionDeclBits = NumDeclBits + 6 }; static_assert(NumExtensionDeclBits <= 32, "fits in an unsigned"); + class IfConfigDeclBitfields { + friend class IfConfigDecl; + unsigned : NumDeclBits; + + /// Whether this decl is missing its closing '#endif'. + unsigned HadMissingEnd : 1; + + /// Whether this condition has been resolved either statically by Parse or + /// later by Condition Resolution. + unsigned HasBeenResolved : 1; + }; + enum { NumIfConfigDeclBits = NumDeclBits + 2 }; + static_assert(NumIfConfigDeclBits <= 32, "fits in an unsigned"); + protected: union { DeclBitfields DeclBits; @@ -601,6 +615,7 @@ class alignas(1 << DeclAlignInBits) Decl { PrecedenceGroupDeclBitfields PrecedenceGroupDeclBits; ImportDeclBitfields ImportDeclBits; ExtensionDeclBitfields ExtensionDeclBits; + IfConfigDeclBitfields IfConfigDeclBits; uint32_t OpaqueBits; }; @@ -1931,14 +1946,16 @@ class IfConfigDecl : public Decl { /// The array is ASTContext allocated. ArrayRef Clauses; SourceLoc EndLoc; - bool HadMissingEnd; - bool HasBeenResolved = false; + public: IfConfigDecl(DeclContext *Parent, ArrayRef Clauses, SourceLoc EndLoc, bool HadMissingEnd) - : Decl(DeclKind::IfConfig, Parent), Clauses(Clauses), - EndLoc(EndLoc), HadMissingEnd(HadMissingEnd) {} + : Decl(DeclKind::IfConfig, Parent), Clauses(Clauses), EndLoc(EndLoc) + { + IfConfigDeclBits.HadMissingEnd = HadMissingEnd; + IfConfigDeclBits.HasBeenResolved = false; + } ArrayRef getClauses() const { return Clauses; } @@ -1955,13 +1972,13 @@ class IfConfigDecl : public Decl { return {}; } - bool isResolved() const { return HasBeenResolved; } - void setResolved() { HasBeenResolved = true; } + bool isResolved() const { return IfConfigDeclBits.HasBeenResolved; } + void setResolved() { IfConfigDeclBits.HasBeenResolved = true; } SourceLoc getEndLoc() const { return EndLoc; } SourceLoc getLoc() const { return Clauses[0].Loc; } - bool hadMissingEnd() const { return HadMissingEnd; } + bool hadMissingEnd() const { return IfConfigDeclBits.HadMissingEnd; } SourceRange getSourceRange() const; diff --git a/include/swift/AST/ModuleLoader.h b/include/swift/AST/ModuleLoader.h index 144cdde1d371e..d14dcfa47f0e7 100644 --- a/include/swift/AST/ModuleLoader.h +++ b/include/swift/AST/ModuleLoader.h @@ -74,6 +74,13 @@ class ModuleLoader { public: virtual ~ModuleLoader() = default; + /// \brief Check whether the module with a given name can be imported without + /// importing it. + /// + /// Note that even if this check succeeds, errors may still occur if the + /// module is loaded in full. + virtual bool canImportModule(std::pair named) = 0; + /// \brief Import a module with the given module path. /// /// \param importLoc The location of the 'import' keyword. diff --git a/include/swift/ClangImporter/ClangImporter.h b/include/swift/ClangImporter/ClangImporter.h index 94a9222e20e50..955e5077a30e8 100644 --- a/include/swift/ClangImporter/ClangImporter.h +++ b/include/swift/ClangImporter/ClangImporter.h @@ -94,6 +94,13 @@ class ClangImporter final : public ClangModuleLoader { ~ClangImporter(); + /// \brief Check whether the module with a given name can be imported without + /// importing it. + /// + /// Note that even if this check succeeds, errors may still occur if the + /// module is loaded in full. + virtual bool canImportModule(std::pair named) override; + /// \brief Import a module with the given module path. /// /// Clang modules will be imported using the Objective-C ARC dialect, diff --git a/include/swift/Sema/SourceLoader.h b/include/swift/Sema/SourceLoader.h index d8619c5f7e1ef..a05dc33a6082e 100644 --- a/include/swift/Sema/SourceLoader.h +++ b/include/swift/Sema/SourceLoader.h @@ -48,6 +48,13 @@ class SourceLoader : public ModuleLoader { SourceLoader &operator=(const SourceLoader &) = delete; SourceLoader &operator=(SourceLoader &&) = delete; + /// \brief Check whether the module with a given name can be imported without + /// importing it. + /// + /// Note that even if this check succeeds, errors may still occur if the + /// module is loaded in full. + virtual bool canImportModule(std::pair named) override; + /// \brief Import a module with the given module path. /// /// \param importLoc The location of the 'import' keyword. diff --git a/include/swift/Serialization/SerializedModuleLoader.h b/include/swift/Serialization/SerializedModuleLoader.h index b2950fd23a4bd..e2bd73804d22b 100644 --- a/include/swift/Serialization/SerializedModuleLoader.h +++ b/include/swift/Serialization/SerializedModuleLoader.h @@ -48,6 +48,13 @@ class SerializedModuleLoader : public ModuleLoader { SerializedModuleLoader &operator=(const SerializedModuleLoader &) = delete; SerializedModuleLoader &operator=(SerializedModuleLoader &&) = delete; + /// \brief Check whether the module with a given name can be imported without + /// importing it. + /// + /// Note that even if this check succeeds, errors may still occur if the + /// module is loaded in full. + virtual bool canImportModule(std::pair named) override; + /// \brief Import a module with the given module path. /// /// \param importLoc The location of the 'import' keyword. diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index 651c5fba3c99b..dea2de9766693 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -1279,6 +1279,20 @@ GenericEnvironment *ASTContext::getOrCreateCanonicalGenericEnvironment( return env; } +bool ASTContext::canImportModule(std::pair ModulePath) { + // If this module has already been successfully imported, it is importable. + if (getLoadedModule(ModulePath) != nullptr) + return true; + + // Otherwise, ask the module loaders. + for (auto &importer : Impl.ModuleLoaders) { + if (importer->canImportModule(ModulePath)) { + return true; + } + } + return false; +} + Module * ASTContext::getModule(ArrayRef> ModulePath) { assert(!ModulePath.empty()); diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 2f85d929c3978..a5d5c105d720f 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -1022,6 +1022,23 @@ bool ClangImporter::isModuleImported(const clang::Module *M) { return M->NameVisibility == clang::Module::NameVisibilityKind::AllVisible; } +bool ClangImporter::canImportModule(std::pair moduleID) { + // Look up the top-level module to see if it exists. + // FIXME: This only works with top-level modules. + auto &clangHeaderSearch = Impl.getClangPreprocessor().getHeaderSearchInfo(); + clang::Module *clangModule = + clangHeaderSearch.lookupModule(moduleID.first.str()); + if (!clangModule) { + return false; + } + + clang::Module::Requirement r; + clang::Module::UnresolvedHeaderDirective mh; + clang::Module *m; + auto &ctx = Impl.getClangASTContext(); + return clangModule->isAvailable(ctx.getLangOpts(), getTargetInfo(), r, mh, m); +} + Module *ClangImporter::loadModule( SourceLoc importLoc, ArrayRef> path) { diff --git a/lib/Parse/ConditionResolver.cpp b/lib/Parse/ConditionResolver.cpp index 3c8beb0daf1cc..4387ab75ac365 100644 --- a/lib/Parse/ConditionResolver.cpp +++ b/lib/Parse/ConditionResolver.cpp @@ -59,8 +59,7 @@ namespace { ConditionClauseResolver(SmallVectorImpl &ExtraTLCDs, SourceFile &SF) - : NDF(SF), ExtraTLCDs(ExtraTLCDs), SF(SF), - Context(SF.getASTContext()) {} + : NDF(SF), ExtraTLCDs(ExtraTLCDs), SF(SF), Context(SF.getASTContext()) {} void resolveClausesAndInsertMembers(IterableDeclContext *Nom, IfConfigDecl *Cond) { @@ -69,9 +68,9 @@ namespace { // Evaluate conditions until we find the active clause. DiagnosticTransaction DT(Context.Diags); auto classification = - Parser::classifyConditionalCompilationExpr(clause.Cond, Context, - Context.Diags, - /*fullCheck*/ true); + Parser::classifyConditionalCompilationExpr(clause.Cond, Context, + Context.Diags, + /*fullCheck*/ true); DT.abort(); if (clause.Cond && !classification.getValueOr(false)) { continue; diff --git a/lib/Parse/ParseStmt.cpp b/lib/Parse/ParseStmt.cpp index 9a7b59f8563cc..e86a5568850a3 100644 --- a/lib/Parse/ParseStmt.cpp +++ b/lib/Parse/ParseStmt.cpp @@ -1789,8 +1789,8 @@ Parser::classifyConditionalCompilationExpr(Expr *condition, if (!fullCheck) { return None; } - return - Context.getModule({ { argumentIdent, UDRE->getLoc() } }) != nullptr; + + return Context.canImportModule({ argumentIdent, UDRE->getLoc() }); } if (!fullCheck) { diff --git a/lib/Sema/SourceLoader.cpp b/lib/Sema/SourceLoader.cpp index 3476677ada731..f532270bb7dc2 100644 --- a/lib/Sema/SourceLoader.cpp +++ b/lib/Sema/SourceLoader.cpp @@ -70,6 +70,22 @@ class SkipNonTransparentFunctions : public DelayedParsingCallbacks { } // unnamed namespace +bool SourceLoader::canImportModule(std::pair ID) { + // Search the memory buffers to see if we can find this file on disk. + FileOrError inputFileOrError = findModule(Ctx, ID.first.str(), + ID.second); + if (!inputFileOrError) { + auto err = inputFileOrError.getError(); + if (err != std::errc::no_such_file_or_directory) { + Ctx.Diags.diagnose(ID.second, diag::sema_opening_import, + ID.first, err.message()); + } + + return false; + } + return true; +} + Module *SourceLoader::loadModule(SourceLoc importLoc, ArrayRef> path) { // FIXME: Swift submodules? diff --git a/lib/Serialization/SerializedModuleLoader.cpp b/lib/Serialization/SerializedModuleLoader.cpp index 9f8c9691eacfd..13a9c44e9f6c0 100644 --- a/lib/Serialization/SerializedModuleLoader.cpp +++ b/lib/Serialization/SerializedModuleLoader.cpp @@ -19,6 +19,7 @@ #include "swift/Basic/SourceManager.h" #include "swift/Basic/Version.h" #include "llvm/ADT/SmallString.h" +#include "llvm/Support/FileSystem.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/Debug.h" @@ -39,15 +40,25 @@ SerializedModuleLoader::~SerializedModuleLoader() = default; static std::error_code openModuleFiles(StringRef DirName, StringRef ModuleFilename, StringRef ModuleDocFilename, - std::unique_ptr &ModuleBuffer, - std::unique_ptr &ModuleDocBuffer, + std::unique_ptr *ModuleBuffer, + std::unique_ptr *ModuleDocBuffer, llvm::SmallVectorImpl &Scratch) { + assert(((ModuleBuffer && ModuleDocBuffer) + || (!ModuleBuffer && !ModuleDocBuffer)) + && "Module and Module Doc buffer must both be initialized or NULL"); // Try to open the module file first. If we fail, don't even look for the // module documentation file. Scratch.clear(); llvm::sys::path::append(Scratch, DirName, ModuleFilename); + // If there are no buffers to load into, simply check for the existence of + // the module file. + if (!(ModuleBuffer || ModuleDocBuffer)) { + return llvm::sys::fs::access(StringRef(Scratch.data(), Scratch.size()), + llvm::sys::fs::AccessMode::Exist); + } + llvm::ErrorOr> ModuleOrErr = - llvm::MemoryBuffer::getFile(StringRef(Scratch.data(), Scratch.size())); + llvm::MemoryBuffer::getFile(StringRef(Scratch.data(), Scratch.size())); if (!ModuleOrErr) return ModuleOrErr.getError(); @@ -56,21 +67,23 @@ openModuleFiles(StringRef DirName, StringRef ModuleFilename, Scratch.clear(); llvm::sys::path::append(Scratch, DirName, ModuleDocFilename); llvm::ErrorOr> ModuleDocOrErr = - llvm::MemoryBuffer::getFile(StringRef(Scratch.data(), Scratch.size())); + llvm::MemoryBuffer::getFile(StringRef(Scratch.data(), Scratch.size())); if (!ModuleDocOrErr && ModuleDocOrErr.getError() != std::errc::no_such_file_or_directory) { return ModuleDocOrErr.getError(); } - ModuleBuffer = std::move(ModuleOrErr.get()); + + *ModuleBuffer = std::move(ModuleOrErr.get()); if (ModuleDocOrErr) - ModuleDocBuffer = std::move(ModuleDocOrErr.get()); + *ModuleDocBuffer = std::move(ModuleDocOrErr.get()); + return std::error_code(); } static bool findModule(ASTContext &ctx, AccessPathElem moduleID, - std::unique_ptr &moduleBuffer, - std::unique_ptr &moduleDocBuffer, + std::unique_ptr *moduleBuffer, + std::unique_ptr *moduleDocBuffer, bool &isFramework) { llvm::SmallString<64> moduleFilename(moduleID.first.str()); moduleFilename += '.'; @@ -95,7 +108,6 @@ findModule(ASTContext &ctx, AccessPathElem moduleID, llvm::SmallString<128> scratch; llvm::SmallString<128> currPath; - isFramework = false; for (auto path : ctx.SearchPathOpts.ImportSearchPaths) { auto err = openModuleFiles(path, @@ -353,6 +365,21 @@ FileUnit *SerializedModuleLoader::loadAST( return nullptr; } +bool +SerializedModuleLoader::canImportModule(std::pair mID) { + // First see if we find it in the registered memory buffers. + if (!MemoryBuffers.empty()) { + auto bufIter = MemoryBuffers.find(mID.first.str()); + if (bufIter != MemoryBuffers.end()) { + return true; + } + } + + // Otherwise look on disk. + bool isFramework = false; + return findModule(Ctx, mID, nullptr, nullptr, isFramework); +} + Module *SerializedModuleLoader::loadModule(SourceLoc importLoc, Module::AccessPathTy path) { // FIXME: Swift submodules? @@ -378,7 +405,7 @@ Module *SerializedModuleLoader::loadModule(SourceLoc importLoc, // Otherwise look on disk. if (!moduleInputBuffer) { - if (!findModule(Ctx, moduleID, moduleInputBuffer, moduleDocInputBuffer, + if (!findModule(Ctx, moduleID, &moduleInputBuffer, &moduleDocInputBuffer, isFramework)) { return nullptr; } diff --git a/test/ClangImporter/Inputs/missing-requirement/module.modulemap b/test/ClangImporter/Inputs/missing-requirement/module.modulemap new file mode 100644 index 0000000000000..d85afa9db552d --- /dev/null +++ b/test/ClangImporter/Inputs/missing-requirement/module.modulemap @@ -0,0 +1,3 @@ +module MissingRequirement { + requires missing +} diff --git a/test/ClangImporter/MixedSource/can_import_objc_idempotent.swift b/test/ClangImporter/MixedSource/can_import_objc_idempotent.swift new file mode 100644 index 0000000000000..e1a481542ccbc --- /dev/null +++ b/test/ClangImporter/MixedSource/can_import_objc_idempotent.swift @@ -0,0 +1,40 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: cp -R %S/Inputs/mixed-target %t + +// FIXME: BEGIN -enable-source-import hackaround +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-module -o %t %clang-importer-sdk-path/swift-modules/CoreGraphics.swift +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-module -o %t %clang-importer-sdk-path/swift-modules/Foundation.swift +// FIXME: END -enable-source-import hackaround + +// 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 +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk-nosource -I %t) -I %S/../Inputs/custom-modules -typecheck %s -verify + +// RUN: rm -rf %t/mixed-target/ +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk-nosource -I %t) -I %S/../Inputs/custom-modules -typecheck %s -verify + +// REQUIRES: objc_interop + +// Test that 'canImport(Foo)' directives do not open symbols from 'Foo' into the +// current module. Only an 'import Foo' statement should do this. + +#if canImport(AppKit) + class AppKitView : NSView {} // expected-error {{use of undeclared type 'NSView'}} +#endif + +#if canImport(UIKit) + class UIKitView : UIView {} // expected-error {{use of undeclared type 'UIView'}} +#endif + +#if canImport(CoreGraphics) + let square = CGRect(x: 100, y: 100, width: 100, height: 100) // expected-error {{use of unresolved identifier 'CGRect'}} + // expected-error@-1 {{'square' used within its own type}} + // expected-error@-2 {{could not infer type for 'square'}} + let (r, s) = square.divided(atDistance: 50, from: .minXEdge) +#endif + +#if canImport(MixedWithHeader) +let object = NSObject() // expected-error {{use of unresolved identifier 'NSObject'}} + // expected-error@-1 {{'object' used within its own type}} + // expected-error@-2 {{could not infer type for 'object'}} +let someAPI = Derived() // expected-error {{use of unresolved identifier 'Derived'}} +#endif diff --git a/test/ClangImporter/can_import_missing_requirement.swift b/test/ClangImporter/can_import_missing_requirement.swift new file mode 100644 index 0000000000000..cd7a80a945387 --- /dev/null +++ b/test/ClangImporter/can_import_missing_requirement.swift @@ -0,0 +1,9 @@ +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -I %S/Inputs/missing-requirement %s -verify + +// REQUIRES: objc_interop + +class Unique {} + +#if canImport(MissingRequirement) + class Unique {} // No diagnostic +#endif diff --git a/test/Parse/ConditionalCompilation/can_import_idempotent.swift b/test/Parse/ConditionalCompilation/can_import_idempotent.swift new file mode 100644 index 0000000000000..c3c3289692751 --- /dev/null +++ b/test/Parse/ConditionalCompilation/can_import_idempotent.swift @@ -0,0 +1,10 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t +// +// RUN: echo "public var dummyVar = Int()" | %target-swift-frontend -module-name DummyModule -emit-module -o %t - +// RUN: %target-swift-frontend -typecheck -verify -I %t %s + +#if canImport(DummyModule) +print(DummyModule.dummyVar) // expected-error {{use of unresolved identifier 'DummyModule'}} +print(dummyVar) // expected-error {{use of unresolved identifier 'dummyVar'}} +#endif