[Dependency Scanning] Specify Source Locations For Missing Module Dependencies

This change modifies the dependency scanner to keep track of source locations of each encountered 'import' statement, in order to be able to emit diagnostics with source locations if an import failed to resolve.

- Keep track of each 'import' statement's source buffer, line number, and column number when adding it. The dependency scanner utilizes separate compilation instances, and therefore separate Source Managers for scanning `import` statements of user sources and textual interfaces of Swift dependencies. Since import resolution may happen in the main scanner compilation instance while the `import` itself was found by an interface-scanning sub-instance, we cannot simply hold on to the import's `SourceLoc`.
- Add libSwiftScan API for diagnostics to carry above source locations to clients.
This commit is contained in:
artemcm
2024-05-10 16:23:49 -07:00
committed by Artem Chikin
parent 6edc0ad1e9
commit 9aeadd0507
16 changed files with 440 additions and 163 deletions

View File

@@ -54,14 +54,15 @@ static void findPath_dfs(ModuleDependencyID X, ModuleDependencyID Y,
std::optional<ModuleDependencyKind> lookupKind = std::nullopt;
// Underlying Clang module needs an explicit lookup to avoid confusing it
// with the parent Swift module.
if ((dep == X.ModuleName && node->isSwiftModule()) || node->isClangModule())
if ((dep.importIdentifier == X.ModuleName && node->isSwiftModule()) ||
node->isClangModule())
lookupKind = ModuleDependencyKind::Clang;
auto optionalDepNode = cache.findDependency(dep, lookupKind);
auto optionalDepNode = cache.findDependency(dep.importIdentifier, lookupKind);
if (!optionalDepNode.has_value())
continue;
auto depNode = optionalDepNode.value();
auto depID = ModuleDependencyID{dep, depNode->getKind()};
auto depID = ModuleDependencyID{dep.importIdentifier, depNode->getKind()};
if (!visited.count(depID)) {
findPath_dfs(depID, Y, visited, stack, result, cache);
}
@@ -89,11 +90,20 @@ findPathToDependency(ModuleDependencyID dependency,
// Diagnose scanner failure and attempt to reconstruct the dependency
// path from the main module to the missing dependency.
static void
diagnoseScannerFailure(StringRef moduleName, DiagnosticEngine &Diags,
diagnoseScannerFailure(const ScannerImportStatementInfo &moduleImport,
DiagnosticEngine &Diags,
const ModuleDependenciesCache &cache,
std::optional<ModuleDependencyID> dependencyOf) {
Diags.diagnose(SourceLoc(), diag::dependency_scan_module_not_found,
moduleName);
SourceLoc importLoc = SourceLoc();
if (!moduleImport.importLocations.empty()) {
auto locInfo = moduleImport.importLocations[0];
importLoc = Diags.SourceMgr.getLocFromExternalSource(locInfo.bufferIdentifier,
locInfo.lineNumber,
locInfo.columnNumber);
}
Diags.diagnose(importLoc, diag::dependency_scan_module_not_found,
moduleImport.importIdentifier);
if (dependencyOf.has_value()) {
auto path = findPathToDependency(dependencyOf.value(), cache);
// We may fail to construct a path in some cases, such as a Swift overlay of
@@ -110,7 +120,7 @@ diagnoseScannerFailure(StringRef moduleName, DiagnosticEngine &Diags,
bool isClang = false;
switch (entryNode->getKind()) {
case swift::ModuleDependencyKind::SwiftSource:
Diags.diagnose(SourceLoc(), diag::dependency_as_imported_by_main_module,
Diags.diagnose(importLoc, diag::dependency_as_imported_by_main_module,
entry.ModuleName);
continue;
case swift::ModuleDependencyKind::SwiftInterface:
@@ -133,13 +143,20 @@ diagnoseScannerFailure(StringRef moduleName, DiagnosticEngine &Diags,
llvm_unreachable("Unexpected dependency kind");
}
// TODO: Consider turning the module file (interface or modulemap) into
// the SourceLoc of this diagnostic instead. Ideally with the location of
// the `import` statement that this dependency originates from.
Diags.diagnose(SourceLoc(), diag::dependency_as_imported_by,
Diags.diagnose(importLoc, diag::dependency_as_imported_by,
entry.ModuleName, moduleFilePath, isClang);
}
}
if (moduleImport.importLocations.size() > 1) {
for (size_t i = 1; i < moduleImport.importLocations.size(); ++i) {
auto locInfo = moduleImport.importLocations[i];
auto importLoc = Diags.SourceMgr.getLocFromExternalSource(locInfo.bufferIdentifier,
locInfo.lineNumber,
locInfo.columnNumber);
Diags.diagnose(importLoc, diag::unresolved_import_location);
}
}
}
ModuleDependencyScanningWorker::ModuleDependencyScanningWorker(
@@ -402,13 +419,15 @@ ModuleDependencyScanner::getMainModuleDependencyInfo(ModuleDecl *mainModule) {
// Add any implicit module names.
for (const auto &import : importInfo.AdditionalUnloadedImports) {
mainDependencies.addModuleImport(import.module.getModulePath(),
&alreadyAddedModules);
&alreadyAddedModules,
&ScanASTContext.SourceMgr);
}
// Already-loaded, implicitly imported module names.
for (const auto &import : importInfo.AdditionalImports) {
mainDependencies.addModuleImport(
import.module.importedModule->getNameStr(), &alreadyAddedModules);
import.module.importedModule->getNameStr(),
&alreadyAddedModules);
}
// Add the bridging header.
@@ -428,18 +447,21 @@ ModuleDependencyScanner::getMainModuleDependencyInfo(ModuleDecl *mainModule) {
// to be impored in the source.
for (const auto &tbdSymbolModule :
ScanCompilerInvocation.getTBDGenOptions().embedSymbolsFromModules) {
mainDependencies.addModuleImport(tbdSymbolModule, &alreadyAddedModules);
mainDependencies.addModuleImport(tbdSymbolModule,
&alreadyAddedModules);
}
}
// Add source-specified `import` dependencies
{
for (auto fileUnit : mainModule->getFiles()) {
auto sf = dyn_cast<SourceFile>(fileUnit);
if (!sf)
auto sourceFile = dyn_cast<SourceFile>(fileUnit);
if (!sourceFile)
continue;
mainDependencies.addModuleImport(*sf, alreadyAddedModules);
mainDependencies.addModuleImports(*sourceFile,
alreadyAddedModules,
&ScanASTContext.SourceMgr);
}
// Add all the successful canImport checks from the ASTContext as part of
@@ -448,8 +470,9 @@ ModuleDependencyScanner::getMainModuleDependencyInfo(ModuleDecl *mainModule) {
// SourceFiles.
for (auto &Module :
mainModule->getASTContext().getSuccessfulCanImportCheckNames())
mainDependencies.addModuleImport(Module.first(), &alreadyAddedModules);
}
mainDependencies.addModuleImport(Module.first(),
&alreadyAddedModules);
}
return mainDependencies;
}
@@ -569,8 +592,9 @@ void ModuleDependencyScanner::resolveImportDependencies(
auto moduleDependencyInfo = optionalModuleDependencyInfo.value();
llvm::StringMap<std::optional<ModuleDependencyVector>> moduleLookupResult;
// ACTDOO: Import refactor
for (const auto &dependsOn : moduleDependencyInfo->getModuleImports())
moduleLookupResult.insert(std::make_pair(dependsOn, std::nullopt));
moduleLookupResult.insert(std::make_pair(dependsOn.importIdentifier, std::nullopt));
// A scanning task to query a module by-name. If the module already exists
// in the cache, do nothing and return.
@@ -599,51 +623,55 @@ void ModuleDependencyScanner::resolveImportDependencies(
moduleLookupResult.insert_or_assign(moduleName, moduleDependencies);
};
// ACTDOO: Import refactor
// Enque asynchronous lookup tasks
for (const auto &dependsOn : moduleDependencyInfo->getModuleImports()) {
bool underlyingClangModuleLookup = moduleID.ModuleName == dependsOn;
bool isTestable = moduleDependencyInfo->isTestableImport(dependsOn);
ScanningThreadPool.async(scanForModuleDependency, getModuleImportIdentifier(dependsOn),
bool underlyingClangModuleLookup = moduleID.ModuleName == dependsOn.importIdentifier;
bool isTestable = moduleDependencyInfo->isTestableImport(dependsOn.importIdentifier);
ScanningThreadPool.async(scanForModuleDependency, getModuleImportIdentifier(dependsOn.importIdentifier),
underlyingClangModuleLookup, isTestable);
}
for (const auto &dependsOn :
moduleDependencyInfo->getOptionalModuleImports()) {
bool underlyingClangModuleLookup = moduleID.ModuleName == dependsOn;
bool isTestable = moduleDependencyInfo->isTestableImport(dependsOn);
ScanningThreadPool.async(scanForModuleDependency, getModuleImportIdentifier(dependsOn),
bool underlyingClangModuleLookup = moduleID.ModuleName == dependsOn.importIdentifier;
bool isTestable = moduleDependencyInfo->isTestableImport(dependsOn.importIdentifier);
ScanningThreadPool.async(scanForModuleDependency, getModuleImportIdentifier(dependsOn.importIdentifier),
underlyingClangModuleLookup, isTestable);
}
ScanningThreadPool.wait();
std::vector<std::string> unresolvedImports;
std::vector<ScannerImportStatementInfo> unresolvedImports;
// Aggregate both previously-cached and freshly-scanned module results
auto recordResolvedModuleImport =
[&cache, &moduleLookupResult, &unresolvedImports, &directDependencies,
moduleID](const std::string &moduleName, bool optionalImport) {
bool underlyingClangModule = moduleID.ModuleName == moduleName;
auto lookupResult = moduleLookupResult[moduleName];
moduleID](const ScannerImportStatementInfo &moduleImport, bool optionalImport) {
bool underlyingClangModule = moduleID.ModuleName == moduleImport.importIdentifier;
auto lookupResult = moduleLookupResult[moduleImport.importIdentifier];
// The imported module was found in the cache
if (lookupResult == std::nullopt) {
const ModuleDependencyInfo *cachedInfo;
if (underlyingClangModule)
cachedInfo =
cache.findDependency(moduleName, ModuleDependencyKind::Clang)
.value();
cache.findDependency(moduleImport.importIdentifier,
ModuleDependencyKind::Clang).value();
else
cachedInfo = cache.findDependency(moduleName).value();
cachedInfo =
cache.findDependency(moduleImport.importIdentifier).value();
assert(cachedInfo && "Expected cached dependency info");
directDependencies.insert({moduleName, cachedInfo->getKind()});
directDependencies.insert({moduleImport.importIdentifier,
cachedInfo->getKind()});
} else {
// Cache discovered module dependencies.
if (!lookupResult.value().empty()) {
cache.recordDependencies(lookupResult.value());
directDependencies.insert(
{moduleName, lookupResult.value()[0].first.Kind});
{moduleImport.importIdentifier,
lookupResult.value()[0].first.Kind});
} else if (!optionalImport) {
// Otherwise, we failed to resolve this dependency. We will try
// again using the cache after all other imports have been resolved.
// If that fails too, a scanning failure will be diagnosed.
unresolvedImports.push_back(moduleName);
unresolvedImports.push_back(moduleImport);
}
}
};
@@ -676,14 +704,16 @@ void ModuleDependencyScanner::resolveImportDependencies(
// transitive dependencies to the cache, and then attempt to re-query imports
// for which resolution originally failed from the cache. If this fails, then
// the scanner genuinely failed to resolve this dependency.
for (const auto &moduleName : unresolvedImports) {
for (const auto &moduleImport : unresolvedImports) {
auto optionalCachedModuleInfo =
cache.findDependency({moduleName, ModuleDependencyKind::Clang});
cache.findDependency({moduleImport.importIdentifier,
ModuleDependencyKind::Clang});
if (optionalCachedModuleInfo.has_value())
directDependencies.insert(
{moduleName, optionalCachedModuleInfo.value()->getKind()});
{moduleImport.importIdentifier,
optionalCachedModuleInfo.value()->getKind()});
else
diagnoseScannerFailure(moduleName, Diagnostics, cache, moduleID);
diagnoseScannerFailure(moduleImport, Diagnostics, cache, moduleID);
}
}