Sema: getImportAccessLevel returns an authoritative import

Change how we pick the one import to point to in diagnostics about a
referenced decl. This mostly affects the warning about superfluously
public imports. This warning encourages the developer to delete imports,
let's make sure we push them towards deleting the right ones.

The order was previously not well defined, except that we always picked
one of the most public imports.

We now prioritize imports in this order:
1. The most public import. (Preserving the current behavior in
   type-checking of access-level on imports)
2. The import of the public version of the module defining the decl,
   determined via export_as or -public-module-name.
3. The import of the module defining the decl.
4. The first import in the sources bringing the decl via reexports.
5. Any other import, usually via an @_exported import in a different file.

rdar://135357155
This commit is contained in:
Alexis Laferrière
2024-09-06 15:03:02 -07:00
parent 8d28ed4fa9
commit 9eaa0e5df8
2 changed files with 222 additions and 3 deletions

View File

@@ -2949,17 +2949,44 @@ SourceFile::getImportAccessLevel(const ModuleDecl *targetModule) const {
assert(targetModule != getParentModule() &&
"getImportAccessLevel doesn't support checking for a self-import");
/// Order of relevancy of `import` to reach `targetModule`.
/// Lower is better/more authoritative.
auto rateImport = [&](const ImportAccessLevel import) -> int {
auto importedModule = import->module.importedModule;
// Prioritize public names:
if (targetModule->getExportAsName() == importedModule->getBaseIdentifier())
return 0;
if (targetModule->getPublicModuleName(/*onlyIfImported*/false) ==
importedModule->getName())
return 1;
// The defining module or overlay:
if (targetModule == importedModule)
return 2;
if (targetModule == importedModule->getUnderlyingModuleIfOverlay())
return 3;
// Any import in the sources.
if (import->importLoc.isValid())
return 4;
return 10;
};
// Find the import with the least restrictive access-level.
// Among those prioritize more relevant one.
auto &imports = getASTContext().getImportCache();
ImportAccessLevel restrictiveImport = std::nullopt;
for (auto &import : *Imports) {
if ((!restrictiveImport.has_value() ||
import.accessLevel > restrictiveImport->accessLevel) &&
import.accessLevel > restrictiveImport->accessLevel ||
(import.accessLevel == restrictiveImport->accessLevel &&
rateImport(import) < rateImport(restrictiveImport))) &&
imports.isImportedBy(targetModule, import.module.importedModule)) {
restrictiveImport = import;
}
}
return restrictiveImport;
}