Skip to content

Sema: Pick the most relevant import for diagnostics about the source of a decl #76358

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 4 commits into from
Sep 10, 2024
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
4 changes: 2 additions & 2 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2672,10 +2672,10 @@ NOTE(add_preconcurrency_to_conformance,none,
"add '@preconcurrency' to the %0 conformance to defer isolation checking to run time", (DeclName))
WARNING(remove_public_import,none,
"public import of %0 was not used in public declarations or inlinable code",
(const ModuleDecl *))
(Identifier))
WARNING(remove_package_import,none,
"package import of %0 was not used in package declarations",
(const ModuleDecl *))
(Identifier))
WARNING(public_decl_needs_sendable,none,
"public %kind0 does not specify whether it is 'Sendable' or not",
(const ValueDecl *))
Expand Down
33 changes: 30 additions & 3 deletions lib/AST/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
3 changes: 3 additions & 0 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2781,6 +2781,9 @@ ClangModuleUnit *ClangImporter::Implementation::getWrapperForModule(
wrapper->setIsSystemModule(underlying->IsSystem);
wrapper->setIsNonSwiftModule();
wrapper->setHasResolvedImports();
if (!underlying->ExportAsModule.empty())
wrapper->setExportAsName(
SwiftContext.getIdentifier(underlying->ExportAsModule));

auto file = new (SwiftContext) ClangModuleUnit(*wrapper, *this,
underlying);
Expand Down
2 changes: 1 addition & 1 deletion lib/Sema/TypeCheckAccess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2552,7 +2552,7 @@ void swift::diagnoseUnnecessaryPublicImports(SourceFile &SF) {
diag::remove_package_import;
auto inFlight = ctx.Diags.diagnose(import.importLoc,
diagId,
importedModule);
importedModule->getName());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not need to use the public name here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want the real module name here, the one that matches the import statement. This would be the diagnostics on a module with an -public-module-name otherwise:

public import SwiftPublicNameCore // expected-warning {{public import of 'SwiftPublicName' was not used in public declarations or inlinable code}}
public import SwiftPublicName

This is not covered anymore since I removed a commit. If I have to fix more things on this PR I'll look to add this back in the test.


if (levelUsed == AccessLevel::Package) {
inFlight.fixItReplace(import.accessLevelRange, "package");
Expand Down
192 changes: 192 additions & 0 deletions test/Sema/authoritative-import-priority.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/// Point to the most relevant import relative to the target decl.
// RUN: %empty-directory(%t)
// RUN: split-file --leading-lines %s %t

/// Build the Swift libraries.
// RUN: %target-swift-frontend -emit-module %t/SwiftPublicNameCore.swift -o %t \
// RUN: -public-module-name SwiftPublicName
// RUN: %target-swift-frontend -emit-module %t/SwiftPublicName.swift -o %t -I %t
// RUN: %target-swift-frontend -emit-module %t/MixedDep.swift -o %t -I %t

/// Client testing order between indirect imports of the same priority.
// RUN: %target-swift-frontend -typecheck -I %t \
// RUN: %t/OrderClient_FileA.swift %t/OrderClient_FileB.swift \
// RUN: -enable-upcoming-feature InternalImportsByDefault \
// RUN: -Rmodule-api-import -verify

/// Client testing order of preference for more levels of imports.
// RUN: %target-swift-frontend -typecheck -I %t \
// RUN: %t/ExportedClient_FileExported.swift %t/ExportedClient_FileA.swift \
// RUN: %t/ExportedClient_FileB.swift %t/ExportedClient_FileC.swift %t/ExportedClient_FileD.swift \
// RUN: -import-underlying-module -module-name ExportedClient \
// RUN: -enable-upcoming-feature InternalImportsByDefault \
// RUN: -Rmodule-api-import -verify

/// Client testing -public-module-name ordering.
// RUN: %target-swift-frontend -typecheck -I %t \
// RUN: %t/SwiftLibClient_FileA.swift %t/SwiftLibClient_FileB.swift \
// RUN: -enable-upcoming-feature InternalImportsByDefault \
// RUN: -Rmodule-api-import -verify

/// Client testing import of the overlay vs an unrelated module.
// RUN: %target-swift-frontend -typecheck -I %t \
// RUN: %t/OverlayClient_FileA.swift %t/OverlayClient_FileB.swift \
// RUN: -enable-upcoming-feature InternalImportsByDefault \
// RUN: -Rmodule-api-import -verify

//--- module.modulemap
module FarClangDep {
header "FarClangDep.h"
}

module MixedDep {
export *
header "MixedDep.h"
}

module IndirectClangDepA {
export *
header "IndirectClangDepA.h"
}

module IndirectClangDepB {
export *
header "IndirectClangDepB.h"
}

module LibCore {
export *
export_as Lib
header "LibCore.h"
}

module Lib {
export *
header "Lib.h"
}

module NotLib {
export *
header "NotLib.h"
}

module ExportedClient {
export *
header "ExportedClient.h"
}

//--- FarClangDep.h
struct FarClangType{};

//--- MixedDep.h
struct UnderlyingType{};

//--- IndirectClangDepA.h
#include <FarClangDep.h>
#include <MixedDep.h>

//--- IndirectClangDepB.h
#include <FarClangDep.h>
#include <MixedDep.h>

//--- LibCore.h
struct ExportedType {};

//--- Lib.h
#include <LibCore.h>

//--- NotLib.h
#include <LibCore.h>

//--- ExportedClient.h
#include <LibCore.h>

//--- SwiftPublicNameCore.swift
public struct SwiftStruct {}

//--- SwiftPublicName.swift
@_exported import SwiftPublicNameCore

//--- MixedDep.swift
@_exported import MixedDep

//--- OrderClient_FileA.swift
/// Between indirect imports, prefer the first one.
public import IndirectClangDepA
public import IndirectClangDepB // expected-warning {{public import of 'IndirectClangDepB' was not used in public declarations or inlinable code}}

public func useTypesB(a: FarClangType) {}
// expected-remark @-1 {{struct 'FarClangType' is imported via 'IndirectClangDepA', which reexports definition from 'FarClangDep'}}

//--- OrderClient_FileB.swift
/// Still prefer the first one after changing the order.
public import IndirectClangDepB
public import IndirectClangDepA // expected-warning {{public import of 'IndirectClangDepA' was not used in public declarations or inlinable code}}

public func useTypesC(a: FarClangType) {}
// expected-remark @-1 {{struct 'FarClangType' is imported via 'IndirectClangDepB', which reexports definition from 'FarClangDep'}}


//--- ExportedClient_FileExported.swift
@_exported public import ExportedClient

//--- ExportedClient_FileA.swift
/// Prefer the defining module.
public import NotLib // expected-warning {{public import of 'NotLib' was not used in public declarations or inlinable code}}
public import LibCore // We should warn here.
public import Lib

public func useTypesA(a: ExportedType) {}
// expected-remark @-1 {{struct 'ExportedType' is imported via 'Lib', which reexports definition from 'LibCore'}}

//--- ExportedClient_FileB.swift
/// Then prefer the export_as module.
public import NotLib // expected-warning {{public import of 'NotLib' was not used in public declarations or inlinable code}}
public import Lib

public func useTypesB(a: ExportedType) {}
// expected-remark @-1 {{struct 'ExportedType' is imported via 'Lib'}}

//--- ExportedClient_FileC.swift
/// Then prefer any local import.
public import NotLib

public func useTypesC(a: ExportedType) {}
// expected-remark @-1 {{struct 'ExportedType' is imported via 'NotLib', which reexports definition from 'LibCore'}}

//--- ExportedClient_FileD.swift
/// Last used the module-wide @_exported import.
public func useTypesD(a: ExportedType) {}
// expected-remark @-1 {{struct 'ExportedType' is imported via 'ExportedClient', which reexports definition from 'LibCore'}}


//--- SwiftLibClient_FileA.swift
/// Prefer the import matching public-module-name.
public import SwiftPublicNameCore // We should warn here.
public import SwiftPublicName

public func useTypesA(a: SwiftStruct) {}
// expected-remark @-1 {{struct 'SwiftStruct' is imported via 'SwiftPublicName', which reexports definition from 'SwiftPublicName'}}

//--- SwiftLibClient_FileB.swift
/// Fallback on read definition site.
public import SwiftPublicNameCore

public func useTypesB(a: SwiftStruct) {}
// expected-remark @-1 {{struct 'SwiftStruct' is imported via 'SwiftPublicName'}}


//--- OverlayClient_FileA.swift
/// Prefer a Swift overlay to an unrelated 3rd module.
public import IndirectClangDepA // expected-warning {{public import of 'IndirectClangDepA' was not used in public declarations or inlinable code}}
public import MixedDep

public func useTypesA(a: UnderlyingType) {}
// expected-remark @-1 {{struct 'UnderlyingType' is imported via 'MixedDep', which reexports definition from 'MixedDep'}}

//--- OverlayClient_FileB.swift
/// Fallback on any reexporter.
public import IndirectClangDepA

public func useTypesB(a: UnderlyingType) {}
// expected-remark @-1 {{struct 'UnderlyingType' is imported via 'IndirectClangDepA', which reexports definition from 'MixedDep'}}