Skip to content

ModuleLoader: teach canImport to check Swift user module versions #37219

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions include/swift/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ class ASTContext final {
DelayedPatternContexts;

/// Cache of module names that fail the 'canImport' test in this context.
llvm::SmallPtrSet<Identifier, 8> FailedModuleImportNames;
mutable llvm::SmallPtrSet<Identifier, 8> FailedModuleImportNames;

/// Retrieve the allocator for the given arena.
llvm::BumpPtrAllocator &
Expand Down Expand Up @@ -894,7 +894,8 @@ class ASTContext final {
/// module is loaded in full.
bool canImportModuleImpl(ImportPath::Element ModulePath,
llvm::VersionTuple version,
bool underlyingVersion) const;
bool underlyingVersion,
bool updateFailingList) const;
public:
namelookup::ImportCache &getImportCache() const;

Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,9 @@ REMARK(module_loaded,none,
"loaded module at %0",
(StringRef))

WARNING(cannot_find_project_version,none,
"cannot find user version number for %0 module '%1'; version number ignored", (StringRef, StringRef))

// Operator decls
ERROR(ambiguous_operator_decls,none,
"ambiguous operator declarations found for operator", ())
Expand Down
13 changes: 10 additions & 3 deletions include/swift/Serialization/SerializedModuleLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,13 @@ class ImplicitSerializedModuleLoader : public SerializedModuleLoaderBase {
/// Imports serialized Swift modules from a MemoryBuffer into an ASTContext.
/// This interface is primarily used by LLDB.
class MemoryBufferSerializedModuleLoader : public SerializedModuleLoaderBase {
llvm::StringMap<std::unique_ptr<llvm::MemoryBuffer>> MemoryBuffers;

struct MemoryBufferInfo {
std::unique_ptr<llvm::MemoryBuffer> buffer;
llvm::VersionTuple userVersion;
};

llvm::StringMap<MemoryBufferInfo> MemoryBuffers;

MemoryBufferSerializedModuleLoader(ASTContext &ctx,
DependencyTracker *tracker,
Expand Down Expand Up @@ -289,8 +295,9 @@ class MemoryBufferSerializedModuleLoader : public SerializedModuleLoaderBase {
///
/// FIXME: make this an actual import *path* once submodules are designed.
void registerMemoryBuffer(StringRef importPath,
std::unique_ptr<llvm::MemoryBuffer> input) {
MemoryBuffers[importPath] = std::move(input);
std::unique_ptr<llvm::MemoryBuffer> input,
llvm::VersionTuple version) {
MemoryBuffers[importPath] = {std::move(input), version};
}

void collectVisibleTopLevelModuleNames(
Expand Down
27 changes: 13 additions & 14 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1957,40 +1957,39 @@ bool ASTContext::shouldPerformTypoCorrection() {

bool ASTContext::canImportModuleImpl(ImportPath::Element ModuleName,
llvm::VersionTuple version,
bool underlyingVersion) const {
// If this module has already been successfully imported, it is importable.
if (getLoadedModule(ImportPath::Module::Builder(ModuleName).get()) != nullptr)
return true;

bool underlyingVersion,
bool updateFailingList) const {
// If we've failed loading this module before, don't look for it again.
if (FailedModuleImportNames.count(ModuleName.Item))
return false;

// If no specific version, the module is importable if it has already been imported.
if (version.empty()) {
// If this module has already been successfully imported, it is importable.
if (getLoadedModule(ImportPath::Module::Builder(ModuleName).get()) != nullptr)
return true;
}
// Otherwise, ask the module loaders.
for (auto &importer : getImpl().ModuleLoaders) {
if (importer->canImportModule(ModuleName, version, underlyingVersion)) {
return true;
}
}

if (updateFailingList && version.empty()) {
FailedModuleImportNames.insert(ModuleName.Item);
}
return false;
}

bool ASTContext::canImportModule(ImportPath::Element ModuleName,
llvm::VersionTuple version,
bool underlyingVersion) {
if (canImportModuleImpl(ModuleName, version, underlyingVersion)) {
return true;
} else {
FailedModuleImportNames.insert(ModuleName.Item);
return false;
}
return canImportModuleImpl(ModuleName, version, underlyingVersion, true);
}

bool ASTContext::canImportModule(ImportPath::Element ModuleName,
llvm::VersionTuple version,
bool underlyingVersion) const {
return canImportModuleImpl(ModuleName, version, underlyingVersion);
return canImportModuleImpl(ModuleName, version, underlyingVersion, false);
}

ModuleDecl *
Expand Down
3 changes: 2 additions & 1 deletion lib/ASTSectionImporter/ASTSectionImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ bool swift::parseASTSection(MemoryBufferSerializedModuleLoader &Loader,
llvm::MemoryBuffer::getMemBuffer(moduleData, info.name, false));

// Register the memory buffer.
Loader.registerMemoryBuffer(info.name, std::move(bitstream));
Loader.registerMemoryBuffer(info.name, std::move(bitstream),
info.userModuleVersion);
foundModules.push_back(info.name.str());
}
} else {
Expand Down
83 changes: 76 additions & 7 deletions lib/Serialization/SerializedModuleLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -962,22 +962,91 @@ bool swift::extractCompilerFlagsFromInterface(StringRef buffer,

bool SerializedModuleLoaderBase::canImportModule(
ImportPath::Element mID, llvm::VersionTuple version, bool underlyingVersion) {
// If underlying version is specified, this should be handled by Clang importer.
if (!version.empty() && underlyingVersion)
return false;
// Look on disk.
SmallVector<char, 0> *unusedModuleInterfacePath = nullptr;
SmallVectorImpl<char> *unusedModuleInterfacePath = nullptr;
std::unique_ptr<llvm::MemoryBuffer> *unusedModuleBuffer = nullptr;
std::unique_ptr<llvm::MemoryBuffer> *unusedModuleDocBuffer = nullptr;
std::unique_ptr<llvm::MemoryBuffer> *unusedModuleSourceInfoBuffer = nullptr;
bool isFramework = false;
bool isSystemModule = false;
return findModule(mID, unusedModuleInterfacePath, unusedModuleBuffer,
unusedModuleDocBuffer, unusedModuleSourceInfoBuffer,
isFramework, isSystemModule);

llvm::SmallString<256> moduleInterfacePath;
std::unique_ptr<llvm::MemoryBuffer> moduleInputBuffer;
std::unique_ptr<llvm::MemoryBuffer> moduleDocBuffer;
if (!version.empty()) {
unusedModuleInterfacePath = &moduleInterfacePath;
unusedModuleBuffer = &moduleInputBuffer;
unusedModuleDocBuffer = &moduleDocBuffer;
}

auto found = findModule(mID, unusedModuleInterfacePath, unusedModuleBuffer,
unusedModuleDocBuffer, unusedModuleSourceInfoBuffer,
isFramework, isSystemModule);
// If we cannot find the module, don't continue.
if (!found)
return false;
// If no version number is specified, don't continue.
if (version.empty())
return true;
assert(found);
assert(!version.empty());
assert(!underlyingVersion);
llvm::VersionTuple currentVersion;
if (!moduleInterfacePath.empty()) {
// Read the inteface file and extract its compiler arguments line
if (auto file = llvm::MemoryBuffer::getFile(moduleInterfacePath)) {
llvm::BumpPtrAllocator alloc;
llvm::StringSaver argSaver(alloc);
SmallVector<const char*, 8> args;
(void)extractCompilerFlagsFromInterface((*file)->getBuffer(), argSaver, args);
for (unsigned I = 0, N = args.size(); I + 1 < N; I++) {
// Check the version number specified via -user-module-version.
StringRef current(args[I]), next(args[I + 1]);
if (current == "-user-module-version") {
currentVersion.tryParse(next);
break;
}
}
}
}
// If failing to extract the user version from the interface file, try the binary
// format, if present.
if (currentVersion.empty() && unusedModuleBuffer) {
auto metaData =
serialization::validateSerializedAST((*unusedModuleBuffer)->getBuffer());
currentVersion = metaData.userModuleVersion;
}

if (currentVersion.empty()) {
Ctx.Diags.diagnose(mID.Loc, diag::cannot_find_project_version, "Swift",
mID.Item.str());
return true;
}

return currentVersion >= version;
}

bool MemoryBufferSerializedModuleLoader::canImportModule(
ImportPath::Element mID, llvm::VersionTuple version, bool underlyingVersion) {
// See if we find it in the registered memory buffers.
return MemoryBuffers.count(mID.Item.str());
// If underlying version is specified, this should be handled by Clang importer.
if (!version.empty() && underlyingVersion)
return false;
auto mIt = MemoryBuffers.find(mID.Item.str());
if (mIt == MemoryBuffers.end())
return false;
if (version.empty())
return true;
if (mIt->second.userVersion.empty()) {
Ctx.Diags.diagnose(mID.Loc, diag::cannot_find_project_version, "Swift",
mID.Item.str());
return true;
}
assert(!version.empty());
assert(!(mIt->second.userVersion.empty()));
return mIt->second.userVersion >= version;
}

ModuleDecl *
Expand Down Expand Up @@ -1055,7 +1124,7 @@ MemoryBufferSerializedModuleLoader::loadModule(SourceLoc importLoc,

bool isFramework = false;
std::unique_ptr<llvm::MemoryBuffer> moduleInputBuffer;
moduleInputBuffer = std::move(bufIter->second);
moduleInputBuffer = std::move(bufIter->second.buffer);
MemoryBuffers.erase(bufIter);
assert(moduleInputBuffer);

Expand Down
58 changes: 58 additions & 0 deletions test/Parse/versioned_canimport.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// RUN: %empty-directory(%t)
// RUN: %empty-directory(%t/textual)
// RUN: %empty-directory(%t/binary)
// RUN: %empty-directory(%t/module-cache)

// RUN: echo "public func foo() {}" > %t/Foo.swift
// RUN: %target-swift-frontend -emit-module %t/Foo.swift -module-name Foo -swift-version 5 -disable-implicit-concurrency-module-import -user-module-version 113.330 -emit-module-interface-path %t/textual/Foo.swiftinterface -enable-library-evolution -emit-module-path %t/binary/Foo.swiftmodule
// RUN: %target-swift-frontend -emit-module %t/Foo.swift -module-name Bar -swift-version 5 -disable-implicit-concurrency-module-import -emit-module-interface-path %t/textual/Bar.swiftinterface -enable-library-evolution -emit-module-path %t/binary/Bar.swiftmodule

// RUN: %target-typecheck-verify-swift -disable-implicit-concurrency-module-import -I %t/textual
// RUN: %target-typecheck-verify-swift -disable-implicit-concurrency-module-import -I %t/binary

import Foo
import Bar

#if canImport(Bar, version: 113.331) // expected-warning {{cannot find user version number for Swift module 'Bar'; version number ignored}}
#endif

#if canImport(Bar, version: 2) // expected-warning {{cannot find user version number for Swift module 'Bar'; version number ignored}}
#endif

func canImportVersioned() {
#if canImport(Foo, version: 113.331)
let a = 1
#endif

#if canImport(Foo, version: 113.3000)
let b = 1
#endif

#if canImport(Foo, version: 114)
let c = 1
#endif

#if canImport(Foo, version: 4)
let d = 1 // expected-warning {{initialization of immutable value 'd' was never used; consider replacing with assignment to '_' or removing it}}
#endif

#if canImport(Foo, version: 113.33)
let e = 1 // expected-warning {{initialization of immutable value 'e' was never used; consider replacing with assignment to '_' or removing it}}
#endif

#if canImport(Foo, underlyingVersion: 113.33)
let ee = 1
#endif

#if canImport(Foo, version: 113.329)
let f = 1 // expected-warning {{initialization of immutable value 'f' was never used; consider replacing with assignment to '_' or removing it}}
#endif

#if canImport(Foo, version: 113.330)
let g = 1 // expected-warning {{initialization of immutable value 'g' was never used; consider replacing with assignment to '_' or removing it}}
#endif

#if canImport(Foo)
let h = 1 // expected-warning {{initialization of immutable value 'h' was never used; consider replacing with assignment to '_' or removing it}}
#endif
}