diff --git a/SwiftCompilerSources/Sources/Basic/SourceLoc.swift b/SwiftCompilerSources/Sources/Basic/SourceLoc.swift index 08426f512ec..796813aad83 100644 --- a/SwiftCompilerSources/Sources/Basic/SourceLoc.swift +++ b/SwiftCompilerSources/Sources/Basic/SourceLoc.swift @@ -10,6 +10,8 @@ // //===----------------------------------------------------------------------===// +import ASTBridging + public struct SourceLoc { /// Points into a source file. let locationInFile: UnsafePointer diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index c227d0b9998..006a5598dfb 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -2903,14 +2903,17 @@ ERROR(decl_from_hidden_module,none, "in an extension with conditional conformances}2; " "%select{%3 has been imported as implementation-only|" "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(decl_from_hidden_module_warn,none, - "cannot use %0 %1 %select{in SPI|as property wrapper in SPI|" - "as result builder in SPI|" + "cannot use %0 %1 %select{here|as property wrapper here|" + "as result builder here|" "in an extension with public or '@usableFromInline' members|" "in an extension with conditional conformances}2; " - "%select{%3 has been imported as implementation-only}4", + "%select{%3 has been imported as implementation-only|" + "<>|<>|" + "%3 was not imported by this file}4", (DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned)) ERROR(conformance_from_implementation_only_module,none, "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 " "because %select{%3 was imported implementation-only|" "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{<>|<>|<>|" + "%3 was not imported by this file}4" + "; this is an error in Swift 6", (DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned)) ERROR(availability_macro_in_inlinable, none, diff --git a/include/swift/AST/SourceFile.h b/include/swift/AST/SourceFile.h index bfd778355e8..3133f3dde7d 100644 --- a/include/swift/AST/SourceFile.h +++ b/include/swift/AST/SourceFile.h @@ -25,6 +25,16 @@ namespace swift { 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. /// /// 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. 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, /// collecting the identifiers in \p spiGroups. diff --git a/lib/AST/Module.cpp b/lib/AST/Module.cpp index 556ca5432fa..522a11e248a 100644 --- a/lib/AST/Module.cpp +++ b/lib/AST/Module.cpp @@ -2438,28 +2438,30 @@ bool SourceFile::hasTestableOrPrivateImport( }); } -bool SourceFile::isImportedImplementationOnly(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; - +RestrictedImportKind SourceFile::getRestrictedImportKind(const ModuleDecl *module) const { auto &imports = getASTContext().getImportCache(); + RestrictedImportKind importKind = RestrictedImportKind::Implicit; // Look at the imports of this source file. for (auto &desc : *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; + } - // If the module is imported this way, it's not imported + // If the module is imported publicly, it's not imported // implementation-only. 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. - return !imports.isImportedBy(module, getParentModule()); + if (imports.isImportedBy(module, getParentModule())) + return RestrictedImportKind::None; + + return importKind; } bool ModuleDecl::isImportedImplementationOnly(const ModuleDecl *module) const { diff --git a/lib/Sema/ResilienceDiagnostics.cpp b/lib/Sema/ResilienceDiagnostics.cpp index 80dbe4fc63d..48fae1829ab 100644 --- a/lib/Sema/ResilienceDiagnostics.cpp +++ b/lib/Sema/ResilienceDiagnostics.cpp @@ -146,7 +146,16 @@ TypeChecker::diagnoseDeclRefExportability(SourceLoc loc, D->diagnose(diag::kind_declared_here, DescriptiveDeclKind::Type); } 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(), fragileKind.getSelector(), definingModule->getName(), static_cast(originKind)); diff --git a/lib/Sema/TypeCheckAccess.cpp b/lib/Sema/TypeCheckAccess.cpp index 64e537f3b40..f3595e58227 100644 --- a/lib/Sema/TypeCheckAccess.cpp +++ b/lib/Sema/TypeCheckAccess.cpp @@ -1503,12 +1503,23 @@ swift::getDisallowedOriginKind(const Decl *decl, downgradeToWarning = DowngradeToWarning::No; ModuleDecl *M = decl->getModuleContext(); 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 // a warning. if (where.isSPI()) 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 // 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 @@ -1529,9 +1540,12 @@ swift::getDisallowedOriginKind(const Decl *decl, continue; } } + auto owningModule = redecl->getOwningModule(); + if (!owningModule) + continue; auto moduleWrapper = decl->getASTContext().getClangModuleLoader()->getWrapperForModule( - redecl->getOwningModule()); + owningModule); auto visibleAccessPath = find_if(sfImportedModules, [&moduleWrapper](auto importedModule) { 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; } else if ((decl->isSPI() || decl->isAvailableAsSPI()) && !where.isSPI()) { if (decl->isAvailableAsSPI() && !decl->isSPI()) { @@ -1883,7 +1900,8 @@ public: const SourceFile *SF = refDecl->getDeclContext()->getParentSourceFile(); ModuleDecl *M = PGD->getModuleContext(); - if (!SF->isImportedImplementationOnly(M)) + RestrictedImportKind howImported = SF->getRestrictedImportKind(M); + if (howImported == RestrictedImportKind::None) return; auto &DE = PGD->getASTContext().Diags; diff --git a/lib/Sema/TypeCheckAccess.h b/lib/Sema/TypeCheckAccess.h index d5ff2e97d7e..a18d15d1db8 100644 --- a/lib/Sema/TypeCheckAccess.h +++ b/lib/Sema/TypeCheckAccess.h @@ -35,12 +35,14 @@ void checkAccessControl(Decl *D); // Problematic origin of an exported type. // // This enum must be kept in sync with +// diag::inlinable_decl_ref_from_hidden_module, // diag::decl_from_hidden_module and // diag::conformance_from_implementation_only_module. enum class DisallowedOriginKind : uint8_t { ImplementationOnly, SPIImported, SPILocal, + ImplicitlyImported, None }; diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index 145916f1aea..56a65b410f3 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -2330,7 +2330,7 @@ void swift::checkImplementationOnlyOverride(const ValueDecl *VD) { assert(SF && "checking a non-source declaration?"); ModuleDecl *M = overridden->getModuleContext(); - if (SF->isImportedImplementationOnly(M)) { + if (SF->getRestrictedImportKind(M) == RestrictedImportKind::ImplementationOnly) { VD->diagnose(diag::implementation_only_override_import_without_attr, overridden->getDescriptiveKind()) .fixItInsert(VD->getAttributeInsertionLoc(false), diff --git a/stdlib/private/OSLog/OSLogFloatingPointTypes.swift b/stdlib/private/OSLog/OSLogFloatingPointTypes.swift index 37efbe6d5a7..e68a2717806 100644 --- a/stdlib/private/OSLog/OSLogFloatingPointTypes.swift +++ b/stdlib/private/OSLog/OSLogFloatingPointTypes.swift @@ -20,6 +20,8 @@ // // "\(x, format: .fixed(precision: 10), privacy: .private\)" +import ObjectiveC + extension OSLogInterpolation { /// Defines interpolation for expressions of type Float. diff --git a/stdlib/private/OSLog/OSLogIntegerTypes.swift b/stdlib/private/OSLog/OSLogIntegerTypes.swift index 306e6230b83..ed5985d8c8b 100644 --- a/stdlib/private/OSLog/OSLogIntegerTypes.swift +++ b/stdlib/private/OSLog/OSLogIntegerTypes.swift @@ -22,6 +22,8 @@ // 1. "\(x, format: .hex, privacy: .private, align: .right\)" // 2. "\(x, format: .hex(minDigits: 10), align: .right(columns: 10)\)" +import ObjectiveC + extension OSLogInterpolation { /// Defines interpolation for expressions of type Int. diff --git a/stdlib/private/OSLog/OSLogStringTypes.swift b/stdlib/private/OSLog/OSLogStringTypes.swift index 6aea1834045..caad2794729 100644 --- a/stdlib/private/OSLog/OSLogStringTypes.swift +++ b/stdlib/private/OSLog/OSLogStringTypes.swift @@ -22,6 +22,8 @@ // 1. "\(x, privacy: .private, align: .right\)" // 2. "\(x, align: .right(columns: 10)\)" +import ObjectiveC + extension OSLogInterpolation { /// Defines interpolation for expressions of type String. diff --git a/test/SPI/implementation_only_spi_import_exposability.swift b/test/SPI/implementation_only_spi_import_exposability.swift index c9e37b82d3a..fbdeab72aa3 100644 --- a/test/SPI/implementation_only_spi_import_exposability.swift +++ b/test/SPI/implementation_only_spi_import_exposability.swift @@ -26,8 +26,8 @@ public protocol IOIProtocol {} @_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 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 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' 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}} @@ -46,8 +46,8 @@ public struct PublicStruct : IOIProtocol, SPIProtocol { // expected-error {{cann } @_spi(B) -public struct LocalSPIStruct : IOIProtocol, SPIProtocol { // expected-warning {{cannot use protocol 'IOIProtocol' in SPI; 'Lib' has been imported as implementation-only}} -// expected-warning @-1 {{cannot use protocol 'SPIProtocol' 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' here; 'Lib' has been imported as implementation-only}} } #endif diff --git a/test/Sema/implicit-import-in-inlinable-code.swift b/test/Sema/implicit-import-in-inlinable-code.swift new file mode 100644 index 00000000000..06b6fb977e9 --- /dev/null +++ b/test/Sema/implicit-import-in-inlinable-code.swift @@ -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() {} +} +