Merge pull request #59799 from xymus/report-export-of-implicitly-imported

[Sema] Report use of implicitly imported decls in inlinable code
This commit is contained in:
Alexis Laferrière
2022-07-01 13:27:18 -07:00
committed by GitHub
13 changed files with 161 additions and 26 deletions

View File

@@ -10,6 +10,8 @@
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
import ASTBridging
public struct SourceLoc { public struct SourceLoc {
/// Points into a source file. /// Points into a source file.
let locationInFile: UnsafePointer<UInt8> let locationInFile: UnsafePointer<UInt8>

View File

@@ -2903,14 +2903,17 @@ ERROR(decl_from_hidden_module,none,
"in an extension with conditional conformances}2; " "in an extension with conditional conformances}2; "
"%select{%3 has been imported as implementation-only|" "%select{%3 has been imported as implementation-only|"
"it is an SPI imported from %3|" "it is an SPI imported from %3|"
"it is SPI}4", "it is SPI|"
"%3 was not imported by this file}4",
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned)) (DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
WARNING(decl_from_hidden_module_warn,none, WARNING(decl_from_hidden_module_warn,none,
"cannot use %0 %1 %select{in SPI|as property wrapper in SPI|" "cannot use %0 %1 %select{here|as property wrapper here|"
"as result builder in SPI|" "as result builder here|"
"in an extension with public or '@usableFromInline' members|" "in an extension with public or '@usableFromInline' members|"
"in an extension with conditional conformances}2; " "in an extension with conditional conformances}2; "
"%select{%3 has been imported as implementation-only}4", "%select{%3 has been imported as implementation-only|"
"<<ERROR>>|<<ERROR>>|"
"%3 was not imported by this file}4",
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned)) (DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
ERROR(conformance_from_implementation_only_module,none, ERROR(conformance_from_implementation_only_module,none,
"cannot use conformance of %0 to %1 %select{here|as property wrapper here|" "cannot use conformance of %0 to %1 %select{here|as property wrapper here|"
@@ -5784,7 +5787,15 @@ ERROR(inlinable_decl_ref_from_hidden_module,
none, "%0 %1 cannot be used in " FRAGILE_FUNC_KIND "2 " none, "%0 %1 cannot be used in " FRAGILE_FUNC_KIND "2 "
"because %select{%3 was imported implementation-only|" "because %select{%3 was imported implementation-only|"
"it is an SPI imported from %3|" "it is an SPI imported from %3|"
"it is SPI}4", "it is SPI|"
"%3 was not imported by this file}4",
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
WARNING(inlinable_decl_ref_from_hidden_module_warn,
none, "%0 %1 cannot be used in " FRAGILE_FUNC_KIND "2 "
"because %select{<<ERROR>>|<<ERROR>>|<<ERROR>>|"
"%3 was not imported by this file}4"
"; this is an error in Swift 6",
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned)) (DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
ERROR(availability_macro_in_inlinable, none, ERROR(availability_macro_in_inlinable, none,

View File

@@ -25,6 +25,16 @@ namespace swift {
class PersistentParserState; class PersistentParserState;
/// Kind of import affecting how a decl can be reexported.
/// This is a subset of \c DisallowedOriginKind.
///
/// \sa getRestrictedImportKind
enum class RestrictedImportKind {
ImplementationOnly,
Implicit,
None // No restriction, i.e. the module is imported publicly.
};
/// A file containing Swift source code. /// A file containing Swift source code.
/// ///
/// This is a .swift or .sil file (or a virtual file, such as the contents of /// This is a .swift or .sil file (or a virtual file, such as the contents of
@@ -336,7 +346,8 @@ public:
/// If not, we can fast-path module checks. /// If not, we can fast-path module checks.
bool hasImplementationOnlyImports() const; bool hasImplementationOnlyImports() const;
bool isImportedImplementationOnly(const ModuleDecl *module) const; /// Get the most permissive restriction applied to the imports of \p module.
RestrictedImportKind getRestrictedImportKind(const ModuleDecl *module) const;
/// Find all SPI names imported from \p importedModule by this file, /// Find all SPI names imported from \p importedModule by this file,
/// collecting the identifiers in \p spiGroups. /// collecting the identifiers in \p spiGroups.

View File

@@ -2438,28 +2438,30 @@ bool SourceFile::hasTestableOrPrivateImport(
}); });
} }
bool SourceFile::isImportedImplementationOnly(const ModuleDecl *module) const { RestrictedImportKind SourceFile::getRestrictedImportKind(const ModuleDecl *module) const {
// Implementation-only imports are (currently) always source-file-specific,
// so if we don't have any, we know the search is complete.
if (!hasImplementationOnlyImports())
return false;
auto &imports = getASTContext().getImportCache(); auto &imports = getASTContext().getImportCache();
RestrictedImportKind importKind = RestrictedImportKind::Implicit;
// Look at the imports of this source file. // Look at the imports of this source file.
for (auto &desc : *Imports) { for (auto &desc : *Imports) {
// Ignore implementation-only imports. // Ignore implementation-only imports.
if (desc.options.contains(ImportFlags::ImplementationOnly)) if (desc.options.contains(ImportFlags::ImplementationOnly)) {
if (imports.isImportedBy(module, desc.module.importedModule))
importKind = RestrictedImportKind::ImplementationOnly;
continue; continue;
}
// If the module is imported this way, it's not imported // If the module is imported publicly, it's not imported
// implementation-only. // implementation-only.
if (imports.isImportedBy(module, desc.module.importedModule)) if (imports.isImportedBy(module, desc.module.importedModule))
return false; return RestrictedImportKind::None;
} }
// Now check this file's enclosing module in case there are re-exports. // Now check this file's enclosing module in case there are re-exports.
return !imports.isImportedBy(module, getParentModule()); if (imports.isImportedBy(module, getParentModule()))
return RestrictedImportKind::None;
return importKind;
} }
bool ModuleDecl::isImportedImplementationOnly(const ModuleDecl *module) const { bool ModuleDecl::isImportedImplementationOnly(const ModuleDecl *module) const {

View File

@@ -146,7 +146,16 @@ TypeChecker::diagnoseDeclRefExportability(SourceLoc loc,
D->diagnose(diag::kind_declared_here, DescriptiveDeclKind::Type); D->diagnose(diag::kind_declared_here, DescriptiveDeclKind::Type);
} else { } else {
ctx.Diags.diagnose(loc, diag::inlinable_decl_ref_from_hidden_module, // Only implicitly imported decls should be reported as a warning,
// and only for language versions below Swift 6.
assert(downgradeToWarning == DowngradeToWarning::No ||
originKind == DisallowedOriginKind::ImplicitlyImported &&
"Only implicitly imported decls should be reported as a warning.");
auto errorOrWarning = downgradeToWarning == DowngradeToWarning::Yes?
diag::inlinable_decl_ref_from_hidden_module_warn:
diag::inlinable_decl_ref_from_hidden_module;
ctx.Diags.diagnose(loc, errorOrWarning,
D->getDescriptiveKind(), D->getName(), D->getDescriptiveKind(), D->getName(),
fragileKind.getSelector(), definingModule->getName(), fragileKind.getSelector(), definingModule->getName(),
static_cast<unsigned>(originKind)); static_cast<unsigned>(originKind));

View File

@@ -1503,12 +1503,23 @@ swift::getDisallowedOriginKind(const Decl *decl,
downgradeToWarning = DowngradeToWarning::No; downgradeToWarning = DowngradeToWarning::No;
ModuleDecl *M = decl->getModuleContext(); ModuleDecl *M = decl->getModuleContext();
auto *SF = where.getDeclContext()->getParentSourceFile(); auto *SF = where.getDeclContext()->getParentSourceFile();
if (SF->isImportedImplementationOnly(M)) {
RestrictedImportKind howImported = SF->getRestrictedImportKind(M);
if (howImported != RestrictedImportKind::None) {
// Temporarily downgrade implementation-only exportability in SPI to // Temporarily downgrade implementation-only exportability in SPI to
// a warning. // a warning.
if (where.isSPI()) if (where.isSPI())
downgradeToWarning = DowngradeToWarning::Yes; downgradeToWarning = DowngradeToWarning::Yes;
// Before Swift 6, implicit imports were not reported unless an
// implementation-only import was also present. Downgrade to a warning
// just in this case.
if (howImported == RestrictedImportKind::Implicit &&
!SF->getASTContext().isSwiftVersionAtLeast(6) &&
!SF->hasImplementationOnlyImports()) {
downgradeToWarning = DowngradeToWarning::Yes;
}
// Even if the current module is @_implementationOnly, Swift should // Even if the current module is @_implementationOnly, Swift should
// not report an error in the cases where the decl is also exported from // not report an error in the cases where the decl is also exported from
// a non @_implementationOnly module. Thus, we check to see if there is // a non @_implementationOnly module. Thus, we check to see if there is
@@ -1529,9 +1540,12 @@ swift::getDisallowedOriginKind(const Decl *decl,
continue; continue;
} }
} }
auto owningModule = redecl->getOwningModule();
if (!owningModule)
continue;
auto moduleWrapper = auto moduleWrapper =
decl->getASTContext().getClangModuleLoader()->getWrapperForModule( decl->getASTContext().getClangModuleLoader()->getWrapperForModule(
redecl->getOwningModule()); owningModule);
auto visibleAccessPath = auto visibleAccessPath =
find_if(sfImportedModules, [&moduleWrapper](auto importedModule) { find_if(sfImportedModules, [&moduleWrapper](auto importedModule) {
return importedModule.importedModule == moduleWrapper || return importedModule.importedModule == moduleWrapper ||
@@ -1543,7 +1557,10 @@ swift::getDisallowedOriginKind(const Decl *decl,
} }
} }
} }
// Implementation-only imported, cannot be reexported.
// Restrictively imported, cannot be reexported.
if (howImported == RestrictedImportKind::Implicit)
return DisallowedOriginKind::ImplicitlyImported;
return DisallowedOriginKind::ImplementationOnly; return DisallowedOriginKind::ImplementationOnly;
} else if ((decl->isSPI() || decl->isAvailableAsSPI()) && !where.isSPI()) { } else if ((decl->isSPI() || decl->isAvailableAsSPI()) && !where.isSPI()) {
if (decl->isAvailableAsSPI() && !decl->isSPI()) { if (decl->isAvailableAsSPI() && !decl->isSPI()) {
@@ -1883,7 +1900,8 @@ public:
const SourceFile *SF = refDecl->getDeclContext()->getParentSourceFile(); const SourceFile *SF = refDecl->getDeclContext()->getParentSourceFile();
ModuleDecl *M = PGD->getModuleContext(); ModuleDecl *M = PGD->getModuleContext();
if (!SF->isImportedImplementationOnly(M)) RestrictedImportKind howImported = SF->getRestrictedImportKind(M);
if (howImported == RestrictedImportKind::None)
return; return;
auto &DE = PGD->getASTContext().Diags; auto &DE = PGD->getASTContext().Diags;

View File

@@ -35,12 +35,14 @@ void checkAccessControl(Decl *D);
// Problematic origin of an exported type. // Problematic origin of an exported type.
// //
// This enum must be kept in sync with // This enum must be kept in sync with
// diag::inlinable_decl_ref_from_hidden_module,
// diag::decl_from_hidden_module and // diag::decl_from_hidden_module and
// diag::conformance_from_implementation_only_module. // diag::conformance_from_implementation_only_module.
enum class DisallowedOriginKind : uint8_t { enum class DisallowedOriginKind : uint8_t {
ImplementationOnly, ImplementationOnly,
SPIImported, SPIImported,
SPILocal, SPILocal,
ImplicitlyImported,
None None
}; };

View File

@@ -2330,7 +2330,7 @@ void swift::checkImplementationOnlyOverride(const ValueDecl *VD) {
assert(SF && "checking a non-source declaration?"); assert(SF && "checking a non-source declaration?");
ModuleDecl *M = overridden->getModuleContext(); ModuleDecl *M = overridden->getModuleContext();
if (SF->isImportedImplementationOnly(M)) { if (SF->getRestrictedImportKind(M) == RestrictedImportKind::ImplementationOnly) {
VD->diagnose(diag::implementation_only_override_import_without_attr, VD->diagnose(diag::implementation_only_override_import_without_attr,
overridden->getDescriptiveKind()) overridden->getDescriptiveKind())
.fixItInsert(VD->getAttributeInsertionLoc(false), .fixItInsert(VD->getAttributeInsertionLoc(false),

View File

@@ -20,6 +20,8 @@
// //
// "\(x, format: .fixed(precision: 10), privacy: .private\)" // "\(x, format: .fixed(precision: 10), privacy: .private\)"
import ObjectiveC
extension OSLogInterpolation { extension OSLogInterpolation {
/// Defines interpolation for expressions of type Float. /// Defines interpolation for expressions of type Float.

View File

@@ -22,6 +22,8 @@
// 1. "\(x, format: .hex, privacy: .private, align: .right\)" // 1. "\(x, format: .hex, privacy: .private, align: .right\)"
// 2. "\(x, format: .hex(minDigits: 10), align: .right(columns: 10)\)" // 2. "\(x, format: .hex(minDigits: 10), align: .right(columns: 10)\)"
import ObjectiveC
extension OSLogInterpolation { extension OSLogInterpolation {
/// Defines interpolation for expressions of type Int. /// Defines interpolation for expressions of type Int.

View File

@@ -22,6 +22,8 @@
// 1. "\(x, privacy: .private, align: .right\)" // 1. "\(x, privacy: .private, align: .right\)"
// 2. "\(x, align: .right(columns: 10)\)" // 2. "\(x, align: .right(columns: 10)\)"
import ObjectiveC
extension OSLogInterpolation { extension OSLogInterpolation {
/// Defines interpolation for expressions of type String. /// Defines interpolation for expressions of type String.

View File

@@ -26,8 +26,8 @@ public protocol IOIProtocol {}
@_spi(A) @_implementationOnly import Lib @_spi(A) @_implementationOnly import Lib
@_spi(B) public func leakSPIStruct(_ a: SPIStruct) -> SPIStruct { fatalError() } // expected-warning 2 {{cannot use struct 'SPIStruct' in SPI; 'Lib' has been imported as implementation-only}} @_spi(B) public func leakSPIStruct(_ a: SPIStruct) -> SPIStruct { fatalError() } // expected-warning 2 {{cannot use struct 'SPIStruct' here; 'Lib' has been imported as implementation-only}}
@_spi(B) public func leakIOIStruct(_ a: IOIStruct) -> IOIStruct { fatalError() } // expected-warning 2 {{cannot use struct 'IOIStruct' in SPI; 'Lib' has been imported as implementation-only}} @_spi(B) public func leakIOIStruct(_ a: IOIStruct) -> IOIStruct { fatalError() } // expected-warning 2 {{cannot use struct 'IOIStruct' here; 'Lib' has been imported as implementation-only}}
public struct PublicStruct : IOIProtocol, SPIProtocol { // expected-error {{cannot use protocol 'IOIProtocol' here; 'Lib' has been imported as implementation-only}} public struct PublicStruct : IOIProtocol, SPIProtocol { // expected-error {{cannot use protocol 'IOIProtocol' here; 'Lib' has been imported as implementation-only}}
// expected-error @-1 {{cannot use protocol 'SPIProtocol' here; 'Lib' has been imported as implementation-only}} // expected-error @-1 {{cannot use protocol 'SPIProtocol' here; 'Lib' has been imported as implementation-only}}
@@ -46,8 +46,8 @@ public struct PublicStruct : IOIProtocol, SPIProtocol { // expected-error {{cann
} }
@_spi(B) @_spi(B)
public struct LocalSPIStruct : IOIProtocol, SPIProtocol { // expected-warning {{cannot use protocol 'IOIProtocol' in SPI; 'Lib' has been imported as implementation-only}} public struct LocalSPIStruct : IOIProtocol, SPIProtocol { // expected-warning {{cannot use protocol 'IOIProtocol' here; 'Lib' has been imported as implementation-only}}
// expected-warning @-1 {{cannot use protocol 'SPIProtocol' in SPI; 'Lib' has been imported as implementation-only}} // expected-warning @-1 {{cannot use protocol 'SPIProtocol' here; 'Lib' has been imported as implementation-only}}
} }
#endif #endif

View File

@@ -0,0 +1,74 @@
/// Report the use in API of indirectly or implicitly imported decls.
// RUN: %empty-directory(%t)
// RUN: %{python} %utils/split_file.py -o %t %s
// RUN: %target-swift-frontend -emit-module %t/empty.swift -module-name empty -o %t/empty.swiftmodule
// RUN: %target-swift-frontend -emit-module %t/libA.swift -module-name libA -o %t/libA.swiftmodule
// RUN: %target-swift-frontend -emit-module %t/libB.swift -module-name libB -o %t/libB.swiftmodule -I %t
/// In pre-Swift 6, this is a warning where there's no implementation-only import present.
// RUN: %target-swift-frontend -emit-module %t/clientFileA-Swift5.swift %t/clientFileB.swift -module-name client -o %t/client.swiftmodule -I %t -verify
/// In pre-Swift 6, this remains an error when there's an implementation-only import present.
// RUN: %target-swift-frontend -emit-module %t/clientFileA-OldCheck.swift %t/clientFileB.swift -module-name client -o %t/client.swiftmodule -I %t -verify
/// In Swift 6, it's an error.
// RUN: %target-swift-frontend -emit-module %t/clientFileA-Swift6.swift %t/clientFileB.swift -module-name client -o %t/client.swiftmodule -I %t -verify -swift-version 6
// BEGIN empty.swift
// BEGIN libA.swift
public struct ImportedType {
public init() {}
}
// BEGIN libB.swift
import libA
extension ImportedType {
public func implicitlyImportedMethod() {}
}
/// Client module
// BEGIN clientFileA-Swift5.swift
import libA
@inlinable public func bar() {
let a = ImportedType()
a.implicitlyImportedMethod() // expected-warning {{instance method 'implicitlyImportedMethod()' cannot be used in an '@inlinable' function because 'libB' was not imported by this file; this is an error in Swift 6}}
// Expected implicit imports are still fine
a.localModuleMethod()
}
// BEGIN clientFileA-OldCheck.swift
import libA
@_implementationOnly import empty
@inlinable public func bar() {
let a = ImportedType()
a.implicitlyImportedMethod() // expected-error {{instance method 'implicitlyImportedMethod()' cannot be used in an '@inlinable' function because 'libB' was not imported by this file}}
// Expected implicit imports are still fine
a.localModuleMethod()
}
// BEGIN clientFileA-Swift6.swift
import libA
@inlinable public func bar() {
let a = ImportedType()
a.implicitlyImportedMethod() // expected-error {{instance method 'implicitlyImportedMethod()' cannot be used in an '@inlinable' function because 'libB' was not imported by this file}}
// Expected implicit imports are still fine
a.localModuleMethod()
}
// BEGIN clientFileB.swift
@_implementationOnly import libB
import libA
extension ImportedType {
public func localModuleMethod() {}
}