Merge pull request #24424 from harlanhaskins/an-absolutely-remarkable-thing

[ModuleInterface] Emit remarks when rebuilding from an interface
This commit is contained in:
Harlan Haskins
2019-05-02 22:45:15 -07:00
committed by GitHub
12 changed files with 284 additions and 31 deletions

View File

@@ -18,8 +18,9 @@
//
//===----------------------------------------------------------------------===//
#if !(defined(DIAG) || (defined(ERROR) && defined(WARNING) && defined(NOTE)))
# error Must define either DIAG or the set {ERROR,WARNING,NOTE}
#if !(defined(DIAG) || (defined(ERROR) && defined(WARNING) && defined(NOTE) && \
defined(REMARK)))
# error Must define either DIAG or the set {ERROR,WARNING,NOTE,REMARK}
#endif
#ifndef ERROR
@@ -37,6 +38,11 @@
DIAG(NOTE,ID,Options,Text,Signature)
#endif
#ifndef REMARK
# define REMARK(ID,Options,Text,Signature) \
DIAG(REMARK,ID,Options,Text,Signature)
#endif
WARNING(warning_no_such_sdk,none,
"no such SDK: '%0'", (StringRef))
@@ -278,6 +284,20 @@ ERROR(error_extracting_flags_from_module_interface,none,
ERROR(missing_dependency_of_module_interface,none,
"missing dependency '%0' of module interface '%1': %2",
(StringRef, StringRef, StringRef))
REMARK(rebuilding_module_from_interface,none,
"rebuilding module '%0' from interface '%1'", (StringRef, StringRef))
NOTE(out_of_date_module_here,none,
"%select{compiled|cached|forwarding|prebuilt}0 module is out of date: '%1'",
(unsigned, StringRef))
NOTE(module_interface_dependency_out_of_date,none,
"dependency is out of date: '%0'",
(StringRef))
NOTE(compiled_module_invalid,none,
"unable to load compiled module '%0'",
(StringRef))
NOTE(compiled_module_invalid_reason,none,
"unable to load compiled module '%0': %1",
(StringRef, StringRef))
ERROR(error_extracting_dependencies_from_cached_module,none,
"error extracting dependencies from cached module '%0'",
(StringRef))
@@ -289,6 +309,7 @@ ERROR(unknown_forced_module_loading_mode,none,
# if defined(DIAG)
# undef DIAG
# endif
# undef REMARK
# undef NOTE
# undef WARNING
# undef ERROR

View File

@@ -272,6 +272,10 @@ public:
/// times) when compiling a module interface?
bool SerializeModuleInterfaceDependencyHashes = false;
/// Should we warn if an imported module needed to be rebuilt from a
/// module interface file?
bool RemarkOnRebuildFromModuleInterface = false;
/// The different modes for validating TBD against the LLVM IR.
enum class TBDValidationMode {
Default, ///< Do the default validation for the current platform.

View File

@@ -122,16 +122,18 @@ namespace swift {
/// directory, and loading the serialized .swiftmodules from there.
class ParseableInterfaceModuleLoader : public SerializedModuleLoaderBase {
friend class unittest::ParseableInterfaceModuleLoaderTest;
explicit ParseableInterfaceModuleLoader(ASTContext &ctx, StringRef cacheDir,
StringRef prebuiltCacheDir,
DependencyTracker *tracker,
ModuleLoadingMode loadMode)
explicit ParseableInterfaceModuleLoader(
ASTContext &ctx, StringRef cacheDir, StringRef prebuiltCacheDir,
DependencyTracker *tracker, ModuleLoadingMode loadMode,
bool RemarkOnRebuildFromInterface)
: SerializedModuleLoaderBase(ctx, tracker, loadMode),
CacheDir(cacheDir), PrebuiltCacheDir(prebuiltCacheDir)
CacheDir(cacheDir), PrebuiltCacheDir(prebuiltCacheDir),
RemarkOnRebuildFromInterface(RemarkOnRebuildFromInterface)
{}
std::string CacheDir;
std::string PrebuiltCacheDir;
bool RemarkOnRebuildFromInterface;
std::error_code findModuleFilesInDirectory(
AccessPathElem ModuleID, StringRef DirPath, StringRef ModuleFilename,
@@ -144,10 +146,12 @@ class ParseableInterfaceModuleLoader : public SerializedModuleLoaderBase {
public:
static std::unique_ptr<ParseableInterfaceModuleLoader>
create(ASTContext &ctx, StringRef cacheDir, StringRef prebuiltCacheDir,
DependencyTracker *tracker, ModuleLoadingMode loadMode) {
DependencyTracker *tracker, ModuleLoadingMode loadMode,
bool RemarkOnRebuildFromInterface = false) {
return std::unique_ptr<ParseableInterfaceModuleLoader>(
new ParseableInterfaceModuleLoader(ctx, cacheDir, prebuiltCacheDir,
tracker, loadMode));
tracker, loadMode,
RemarkOnRebuildFromInterface));
}
/// Unconditionally build \p InPath (a swiftinterface file) to \p OutPath (as

View File

@@ -388,6 +388,9 @@ def warn_long_expression_type_checking : Separate<["-"], "warn-long-expression-t
def warn_long_expression_type_checking_EQ : Joined<["-"], "warn-long-expression-type-checking=">,
Alias<warn_long_expression_type_checking>;
def Rmodule_interface_rebuild : Flag<["-"], "Rmodule-interface-rebuild">,
HelpText<"Emits a remark if an imported module needs to be re-compiled from its module interface">;
def solver_expression_time_threshold_EQ : Joined<["-"], "solver-expression-time-threshold=">;
def solver_disable_shrink :

View File

@@ -83,6 +83,9 @@ bool ArgsToFrontendOptionsConverter::convert(
Opts.SerializeModuleInterfaceDependencyHashes |=
Args.hasArg(OPT_serialize_module_interface_dependency_hashes);
Opts.RemarkOnRebuildFromModuleInterface |=
Args.hasArg(OPT_Rmodule_interface_rebuild);
computePrintStatsOptions();
computeDebugTimeOptions();
computeTBDOptions();

View File

@@ -246,6 +246,9 @@ bool DiagnosticVerifier::verifyFile(unsigned BufferID,
} else if (MatchStart.startswith("expected-error")) {
ExpectedClassification = llvm::SourceMgr::DK_Error;
MatchStart = MatchStart.substr(strlen("expected-error"));
} else if (MatchStart.startswith("expected-remark")) {
ExpectedClassification = llvm::SourceMgr::DK_Remark;
MatchStart = MatchStart.substr(strlen("expected-remark"));
} else
continue;

View File

@@ -339,13 +339,11 @@ bool CompilerInstance::setUpModuleLoaders() {
if (MLM != ModuleLoadingMode::OnlySerialized) {
auto const &Clang = clangImporter->getClangInstance();
std::string ModuleCachePath = getModuleCachePathFromClang(Clang);
StringRef PrebuiltModuleCachePath =
Invocation.getFrontendOptions().PrebuiltModuleCachePath;
auto PIML = ParseableInterfaceModuleLoader::create(*Context,
ModuleCachePath,
PrebuiltModuleCachePath,
getDependencyTracker(),
MLM);
auto &FEOpts = Invocation.getFrontendOptions();
StringRef PrebuiltModuleCachePath = FEOpts.PrebuiltModuleCachePath;
auto PIML = ParseableInterfaceModuleLoader::create(
*Context, ModuleCachePath, PrebuiltModuleCachePath,
getDependencyTracker(), MLM, FEOpts.RemarkOnRebuildFromModuleInterface);
Context->addModuleLoader(std::move(PIML));
}
Context->addModuleLoader(std::move(SML));

View File

@@ -621,6 +621,100 @@ public:
namespace {
/// Keeps track of the various reasons the module interface loader needed to
/// fall back and rebuild a module from its interface.
struct ModuleRebuildInfo {
enum class ModuleKind {
Normal,
Cached,
Forwarding,
Prebuilt
};
struct OutOfDateModule {
std::string path;
Optional<serialization::Status> serializationStatus;
ModuleKind kind;
SmallVector<std::string, 10> outOfDateDependencies;
};
SmallVector<OutOfDateModule, 3> outOfDateModules;
OutOfDateModule &getOrInsertOutOfDateModule(StringRef path) {
for (auto &mod : outOfDateModules) {
if (mod.path == path) return mod;
}
outOfDateModules.push_back({path, None, ModuleKind::Normal, {}});
return outOfDateModules.back();
}
/// Sets the kind of a module that failed to load.
void setModuleKind(StringRef path, ModuleKind kind) {
getOrInsertOutOfDateModule(path).kind = kind;
}
/// Sets the serialization status of the module at \c path. If this is
/// anything other than \c Valid, a note will be added stating why the module
/// was invalid.
void setSerializationStatus(StringRef path, serialization::Status status) {
getOrInsertOutOfDateModule(path).serializationStatus = status;
}
/// Registers an out-of-date dependency at \c depPath for the module
/// at \c modulePath.
void addOutOfDateDependency(StringRef modulePath, StringRef depPath) {
getOrInsertOutOfDateModule(modulePath)
.outOfDateDependencies.push_back(depPath);
}
const char *invalidModuleReason(serialization::Status status) {
using namespace serialization;
switch (status) {
case Status::FormatTooOld:
return "compiled with an older version of the compiler";
case Status::FormatTooNew:
return "compiled with a newer version of the compiler";
case Status::Malformed:
return "malformed";
case Status::TargetIncompatible:
return "compiled for a different target platform";
case Status::TargetTooNew:
return "target platform newer than current platform";
default: return nullptr;
}
}
/// Emits a diagnostic for all out-of-date compiled or forwarding modules
/// encountered while trying to load a module.
void diagnose(ASTContext &ctx, SourceLoc loc, StringRef moduleName,
StringRef interfacePath) {
ctx.Diags.diagnose(loc, diag::rebuilding_module_from_interface,
moduleName, interfacePath);
// We may have found multiple failing modules, that failed for different
// reasons. Emit a note for each of them.
for (auto &mod : outOfDateModules) {
ctx.Diags.diagnose(loc, diag::out_of_date_module_here,
(unsigned)mod.kind, mod.path);
// Diagnose any out-of-date dependencies in this module.
for (auto &dep : mod.outOfDateDependencies) {
ctx.Diags.diagnose(loc, diag::module_interface_dependency_out_of_date,
dep);
}
// If there was a compiled module that wasn't able to be read, diagnose
// the reason we couldn't read it.
if (auto status = mod.serializationStatus) {
if (auto reason = invalidModuleReason(*status)) {
ctx.Diags.diagnose(loc, diag::compiled_module_invalid_reason,
mod.path, reason);
} else {
ctx.Diags.diagnose(loc, diag::compiled_module_invalid, mod.path);
}
}
}
}
};
/// Handles the details of loading parseable interfaces as modules, and will
/// do the necessary lookup to determine if we should be loading from the
/// normal cache, the prebuilt cache, a module adjacent to the interface, or
@@ -631,6 +725,7 @@ class ParseableInterfaceModuleLoaderImpl {
ASTContext &ctx;
llvm::vfs::FileSystem &fs;
DiagnosticEngine &diags;
ModuleRebuildInfo rebuildInfo;
const StringRef modulePath;
const std::string interfacePath;
const StringRef moduleName;
@@ -639,17 +734,20 @@ class ParseableInterfaceModuleLoaderImpl {
const SourceLoc diagnosticLoc;
DependencyTracker *const dependencyTracker;
const ModuleLoadingMode loadMode;
const bool remarkOnRebuildFromInterface;
ParseableInterfaceModuleLoaderImpl(
ASTContext &ctx, StringRef modulePath, StringRef interfacePath,
StringRef moduleName, StringRef cacheDir, StringRef prebuiltCacheDir,
SourceLoc diagLoc, DependencyTracker *dependencyTracker = nullptr,
SourceLoc diagLoc, bool remarkOnRebuildFromInterface,
DependencyTracker *dependencyTracker = nullptr,
ModuleLoadingMode loadMode = ModuleLoadingMode::PreferSerialized)
: ctx(ctx), fs(*ctx.SourceMgr.getFileSystem()), diags(ctx.Diags),
modulePath(modulePath), interfacePath(interfacePath),
moduleName(moduleName), prebuiltCacheDir(prebuiltCacheDir),
cacheDir(cacheDir), diagnosticLoc(diagLoc),
dependencyTracker(dependencyTracker), loadMode(loadMode) {}
dependencyTracker(dependencyTracker), loadMode(loadMode),
remarkOnRebuildFromInterface(remarkOnRebuildFromInterface) {}
/// Construct a cache key for the .swiftmodule being generated. There is a
/// balance to be struck here between things that go in the cache key and
@@ -745,13 +843,15 @@ class ParseableInterfaceModuleLoaderImpl {
// Check if all the provided file dependencies are up-to-date compared to
// what's currently on disk.
bool dependenciesAreUpToDate(ArrayRef<FileDependency> deps) {
bool dependenciesAreUpToDate(StringRef modulePath,
ArrayRef<FileDependency> deps) {
SmallString<128> SDKRelativeBuffer;
for (auto &in : deps) {
StringRef fullPath = getFullDependencyPath(in, SDKRelativeBuffer);
if (!dependencyIsUpToDate(in, fullPath)) {
LLVM_DEBUG(llvm::dbgs() << "Dep " << fullPath
<< " is directly out of date\n");
rebuildInfo.addOutOfDateDependency(modulePath, fullPath);
return false;
}
LLVM_DEBUG(llvm::dbgs() << "Dep " << fullPath << " is up to date\n");
@@ -762,15 +862,18 @@ class ParseableInterfaceModuleLoaderImpl {
// Check that the output .swiftmodule file is at least as new as all the
// dependencies it read when it was built last time.
bool serializedASTBufferIsUpToDate(
const llvm::MemoryBuffer &buf, SmallVectorImpl<FileDependency> &allDeps) {
StringRef path, const llvm::MemoryBuffer &buf,
SmallVectorImpl<FileDependency> &allDeps) {
LLVM_DEBUG(llvm::dbgs() << "Validating deps of " << modulePath << "\n");
auto validationInfo = serialization::validateSerializedAST(
buf.getBuffer(), /*ExtendedValidationInfo=*/nullptr, &allDeps);
if (validationInfo.status != serialization::Status::Valid)
if (validationInfo.status != serialization::Status::Valid) {
rebuildInfo.setSerializationStatus(path, validationInfo.status);
return false;
}
return dependenciesAreUpToDate(allDeps);
return dependenciesAreUpToDate(path, allDeps);
}
// Check that the output .swiftmodule file is at least as new as all the
@@ -782,14 +885,15 @@ class ParseableInterfaceModuleLoaderImpl {
if (!OutBuf)
return false;
moduleBuffer = std::move(*OutBuf);
return serializedASTBufferIsUpToDate(*moduleBuffer, AllDeps);
return serializedASTBufferIsUpToDate(modulePath, *moduleBuffer, AllDeps);
}
// Check that a "forwarding" .swiftmodule file is at least as new as all the
// dependencies it read when it was built last time. Requires that the
// forwarding module has been loaded from disk.
bool forwardingModuleIsUpToDate(
const ForwardingModule &fwd, SmallVectorImpl<FileDependency> &deps,
StringRef path, const ForwardingModule &fwd,
SmallVectorImpl<FileDependency> &deps,
std::unique_ptr<llvm::MemoryBuffer> &moduleBuffer) {
// First, make sure the underlying module path exists and is valid.
auto modBuf = fs.getBufferForFile(fwd.underlyingModulePath);
@@ -805,7 +909,7 @@ class ParseableInterfaceModuleLoaderImpl {
dep.path, /*isSDKRelative=*/false, dep.size,
dep.lastModificationTime));
}
if (!dependenciesAreUpToDate(deps))
if (!dependenciesAreUpToDate(path, deps))
return false;
moduleBuffer = std::move(*modBuf);
@@ -924,6 +1028,7 @@ class ParseableInterfaceModuleLoaderImpl {
llvm_unreachable("module interface loader should not have been created");
}
// First, check the cached module path. Whatever's in this cache represents
// the most up-to-date knowledge we have about the module.
if (auto cachedBufOrError = fs.getBufferForFile(cachedOutputPath)) {
@@ -939,7 +1044,8 @@ class ParseableInterfaceModuleLoaderImpl {
if (isForwardingModule) {
if (auto forwardingModule = ForwardingModule::load(*buf)) {
std::unique_ptr<llvm::MemoryBuffer> moduleBuffer;
if (forwardingModuleIsUpToDate(*forwardingModule, deps,
if (forwardingModuleIsUpToDate(cachedOutputPath,
*forwardingModule, deps,
moduleBuffer)) {
LLVM_DEBUG(llvm::dbgs() << "Found up-to-date forwarding module at "
<< cachedOutputPath << "\n");
@@ -949,15 +1055,19 @@ class ParseableInterfaceModuleLoaderImpl {
LLVM_DEBUG(llvm::dbgs() << "Found out-of-date forwarding module at "
<< cachedOutputPath << "\n");
rebuildInfo.setModuleKind(cachedOutputPath,
ModuleRebuildInfo::ModuleKind::Forwarding);
}
// Otherwise, check if the AST buffer itself is up to date.
} else if (serializedASTBufferIsUpToDate(*buf, deps)) {
} else if (serializedASTBufferIsUpToDate(cachedOutputPath, *buf, deps)) {
LLVM_DEBUG(llvm::dbgs() << "Found up-to-date cached module at "
<< cachedOutputPath << "\n");
return DiscoveredModule::normal(cachedOutputPath, std::move(buf));
} else {
LLVM_DEBUG(llvm::dbgs() << "Found out-of-date cached module at "
<< cachedOutputPath << "\n");
rebuildInfo.setModuleKind(cachedOutputPath,
ModuleRebuildInfo::ModuleKind::Cached);
}
}
@@ -983,6 +1093,8 @@ class ParseableInterfaceModuleLoaderImpl {
} else {
LLVM_DEBUG(llvm::dbgs() << "Found out-of-date prebuilt module at "
<< path->str() << "\n");
rebuildInfo.setModuleKind(*path,
ModuleRebuildInfo::ModuleKind::Prebuilt);
}
}
}
@@ -999,7 +1111,8 @@ class ParseableInterfaceModuleLoaderImpl {
auto adjacentModuleBuffer = fs.getBufferForFile(modulePath);
if (adjacentModuleBuffer) {
if (serializedASTBufferIsUpToDate(*adjacentModuleBuffer.get(), deps)) {
if (serializedASTBufferIsUpToDate(modulePath, *adjacentModuleBuffer.get(),
deps)) {
LLVM_DEBUG(llvm::dbgs() << "Found up-to-date module at "
<< modulePath
<< "; deferring to serialized module loader\n");
@@ -1022,6 +1135,8 @@ class ParseableInterfaceModuleLoaderImpl {
} else {
LLVM_DEBUG(llvm::dbgs() << "Found out-of-date module at "
<< modulePath << "\n");
rebuildInfo.setModuleKind(modulePath,
ModuleRebuildInfo::ModuleKind::Normal);
}
} else if (adjacentModuleBuffer.getError() != notFoundError) {
return std::make_error_code(std::errc::not_supported);
@@ -1159,7 +1274,15 @@ class ParseableInterfaceModuleLoaderImpl {
}
std::unique_ptr<llvm::MemoryBuffer> moduleBuffer;
// We didn't discover a module corresponding to this interface. Build one.
// We didn't discover a module corresponding to this interface.
// Diagnose that we didn't find a loadable module, if we were asked to.
if (remarkOnRebuildFromInterface) {
rebuildInfo.diagnose(ctx, diagnosticLoc, moduleName,
interfacePath);
}
if (builder.buildSwiftModule(cachedOutputPath, /*shouldSerializeDeps*/true,
&moduleBuffer))
return std::make_error_code(std::errc::invalid_argument);
@@ -1214,7 +1337,8 @@ std::error_code ParseableInterfaceModuleLoader::findModuleFilesInDirectory(
// Create an instance of the Impl to do the heavy lifting.
ParseableInterfaceModuleLoaderImpl Impl(
Ctx, ModPath, InPath, ModuleID.first.str(),
CacheDir, PrebuiltCacheDir, ModuleID.second, dependencyTracker,
CacheDir, PrebuiltCacheDir, ModuleID.second,
RemarkOnRebuildFromInterface, dependencyTracker,
LoadMode);
// Ask the impl to find us a module that we can load or give us an error

View File

@@ -0,0 +1,19 @@
// RUN: %empty-directory(%t/ModuleCache)
// RUN: %empty-directory(%t/Build)
// RUN: %empty-directory(%t/PrebuiltCache)
// 1. Create a dummy module
// RUN: echo 'public func publicFunction() {}' > %t/TestModule.swift
// 2. Create an interface for it
// RUN: %target-swift-frontend -typecheck %t/TestModule.swift -emit-module-interface-path %t/Build/TestModule.swiftinterface -swift-version 5
// 3. Create an empty .swiftmodule, which will force recompiling from the interface
// RUN: touch %t/Build/TestModule.swiftmodule
// 4. Try to import the malformed compiled module
// RUN: %target-swift-frontend -typecheck -verify %s -I %t/Build -Rmodule-interface-rebuild -module-cache-path %t/ModuleCache
import TestModule // expected-remark {{rebuilding module 'TestModule' from interface}}
// expected-note @-1 {{is out of date}}
// expected-note @-2 {{malformed}}

View File

@@ -0,0 +1,21 @@
// RUN: %empty-directory(%t/ModuleCache)
// RUN: %empty-directory(%t/Build)
// 1. Create a dummy module
// RUN: echo 'public func publicFunction() {}' > %t/TestModule.swift
// 2. Create an interface for it
// RUN: %target-swift-frontend -typecheck %t/TestModule.swift -emit-module-interface-path %t/Build/TestModule.swiftinterface -swift-version 5
// 3. Try to import the interface, which will pass and create a cached module
// RUN: %target-swift-frontend -typecheck %s -I %t/Build -module-cache-path %t/ModuleCache
// 4. Touch the interface so the cached module is no longer up-to-date
// RUN: touch %t/Build/TestModule.swiftinterface
// 5. Try to import the now out-of-date cached module
// RUN: %target-swift-frontend -typecheck -verify %s -I %t/Build -Rmodule-interface-rebuild -module-cache-path %t/ModuleCache -Xllvm -debug-only=textual-module-interface
import TestModule // expected-remark {{rebuilding module 'TestModule' from interface}}
// expected-note @-1 {{cached module is out of date}}
// expected-note @-2 {{dependency is out of date}}

View File

@@ -0,0 +1,21 @@
// RUN: %empty-directory(%t/ModuleCache)
// RUN: %empty-directory(%t/Build)
// 1. Create a dummy module
// RUN: echo 'public func publicFunction() {}' > %t/TestModule.swift
// 2. Create an interface for it
// RUN: %target-swift-frontend -typecheck %t/TestModule.swift -emit-module-interface-path %t/Build/TestModule.swiftinterface -swift-version 5
// 3. Build the .swiftinterface to a .swiftmodule, which will have a dependency on the interface
// RUN: %target-swift-frontend -compile-module-from-interface -o %t/Build/TestModule.swiftmodule %t/Build/TestModule.swiftinterface
// 4. Touch the interface so the module is no longer up-to-date
// RUN: touch %t/Build/TestModule.swiftinterface
// 5. Try to import the out-of-date compiled module
// RUN: %target-swift-frontend -typecheck -verify %s -I %t/Build -Rmodule-interface-rebuild -module-cache-path %t/ModuleCache
import TestModule // expected-remark {{rebuilding module 'TestModule' from interface}}
// expected-note @-1 {{compiled module is out of date}}
// expected-note @-2 {{dependency is out of date}}

View File

@@ -0,0 +1,32 @@
// RUN: %empty-directory(%t/ModuleCache)
// RUN: %empty-directory(%t/Build)
// RUN: %empty-directory(%t/PrebuiltCache)
// 1. Create a dummy module
// RUN: echo 'public func publicFunction() {}' > %t/TestModule.swift
// 2. Create an interface for it
// RUN: %target-swift-frontend -typecheck %t/TestModule.swift -emit-module-interface-path %t/Build/TestModule.swiftinterface -swift-version 5
// 3. Build the .swiftinterface to a .swiftmodule in the prebuilt cache, which will have a dependency on the interface
// RUN: %target-swift-frontend -compile-module-from-interface %t/Build/TestModule.swiftinterface -o %t/PrebuiltCache/TestModule.swiftmodule
// 5. Try to import the prebuilt module (this should pass)
// RUN: %target-swift-frontend -typecheck %s -I %t/Build -sdk %t -prebuilt-module-cache-path %t/PrebuiltCache -module-cache-path %t/ModuleCache
// 6. Make sure we installed a forwarding module in the cache
// RUN: %{python} %S/../Inputs/check-is-forwarding-module.py %t/ModuleCache/TestModule-*.swiftmodule
// 7. Modify the interface so the forwarding module and prebuilt modules are no longer up-to-date
// RUN: echo ' ' >> %t/Build/TestModule.swiftinterface
// 8. Try to import the now out-of-date forwarding module, which will fail.
// It will also fail to load the prebuilt module after the forwarding module
// is rejected, meaning we'll get a second set of notes about the prebuilt module.
// RUN: %target-swift-frontend -typecheck -verify %s -I %t/Build -Rmodule-interface-rebuild -sdk %t -prebuilt-module-cache-path %t/PrebuiltCache -module-cache-path %t/ModuleCache
import TestModule // expected-remark {{rebuilding module 'TestModule' from interface}}
// expected-note @-1 {{forwarding module is out of date}}
// expected-note @-2 {{dependency is out of date}}
// expected-note @-3 {{prebuilt module is out of date}}
// expected-note @-4 {{dependency is out of date}}