Skip to content

Commit 2bc2021

Browse files
authored
Merge pull request #59799 from xymus/report-export-of-implicitly-imported
[Sema] Report use of implicitly imported decls in inlinable code
2 parents e34eda4 + a89b71d commit 2bc2021

13 files changed

+161
-26
lines changed

SwiftCompilerSources/Sources/Basic/SourceLoc.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import ASTBridging
14+
1315
public struct SourceLoc {
1416
/// Points into a source file.
1517
let locationInFile: UnsafePointer<UInt8>

include/swift/AST/DiagnosticsSema.def

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2903,14 +2903,17 @@ ERROR(decl_from_hidden_module,none,
29032903
"in an extension with conditional conformances}2; "
29042904
"%select{%3 has been imported as implementation-only|"
29052905
"it is an SPI imported from %3|"
2906-
"it is SPI}4",
2906+
"it is SPI|"
2907+
"%3 was not imported by this file}4",
29072908
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
29082909
WARNING(decl_from_hidden_module_warn,none,
2909-
"cannot use %0 %1 %select{in SPI|as property wrapper in SPI|"
2910-
"as result builder in SPI|"
2910+
"cannot use %0 %1 %select{here|as property wrapper here|"
2911+
"as result builder here|"
29112912
"in an extension with public or '@usableFromInline' members|"
29122913
"in an extension with conditional conformances}2; "
2913-
"%select{%3 has been imported as implementation-only}4",
2914+
"%select{%3 has been imported as implementation-only|"
2915+
"<<ERROR>>|<<ERROR>>|"
2916+
"%3 was not imported by this file}4",
29142917
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
29152918
ERROR(conformance_from_implementation_only_module,none,
29162919
"cannot use conformance of %0 to %1 %select{here|as property wrapper here|"
@@ -5784,7 +5787,15 @@ ERROR(inlinable_decl_ref_from_hidden_module,
57845787
none, "%0 %1 cannot be used in " FRAGILE_FUNC_KIND "2 "
57855788
"because %select{%3 was imported implementation-only|"
57865789
"it is an SPI imported from %3|"
5787-
"it is SPI}4",
5790+
"it is SPI|"
5791+
"%3 was not imported by this file}4",
5792+
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
5793+
5794+
WARNING(inlinable_decl_ref_from_hidden_module_warn,
5795+
none, "%0 %1 cannot be used in " FRAGILE_FUNC_KIND "2 "
5796+
"because %select{<<ERROR>>|<<ERROR>>|<<ERROR>>|"
5797+
"%3 was not imported by this file}4"
5798+
"; this is an error in Swift 6",
57885799
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
57895800

57905801
ERROR(availability_macro_in_inlinable, none,

include/swift/AST/SourceFile.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ namespace swift {
2525

2626
class PersistentParserState;
2727

28+
/// Kind of import affecting how a decl can be reexported.
29+
/// This is a subset of \c DisallowedOriginKind.
30+
///
31+
/// \sa getRestrictedImportKind
32+
enum class RestrictedImportKind {
33+
ImplementationOnly,
34+
Implicit,
35+
None // No restriction, i.e. the module is imported publicly.
36+
};
37+
2838
/// A file containing Swift source code.
2939
///
3040
/// This is a .swift or .sil file (or a virtual file, such as the contents of
@@ -336,7 +346,8 @@ class SourceFile final : public FileUnit {
336346
/// If not, we can fast-path module checks.
337347
bool hasImplementationOnlyImports() const;
338348

339-
bool isImportedImplementationOnly(const ModuleDecl *module) const;
349+
/// Get the most permissive restriction applied to the imports of \p module.
350+
RestrictedImportKind getRestrictedImportKind(const ModuleDecl *module) const;
340351

341352
/// Find all SPI names imported from \p importedModule by this file,
342353
/// collecting the identifiers in \p spiGroups.

lib/AST/Module.cpp

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2438,28 +2438,30 @@ bool SourceFile::hasTestableOrPrivateImport(
24382438
});
24392439
}
24402440

2441-
bool SourceFile::isImportedImplementationOnly(const ModuleDecl *module) const {
2442-
// Implementation-only imports are (currently) always source-file-specific,
2443-
// so if we don't have any, we know the search is complete.
2444-
if (!hasImplementationOnlyImports())
2445-
return false;
2446-
2441+
RestrictedImportKind SourceFile::getRestrictedImportKind(const ModuleDecl *module) const {
24472442
auto &imports = getASTContext().getImportCache();
2443+
RestrictedImportKind importKind = RestrictedImportKind::Implicit;
24482444

24492445
// Look at the imports of this source file.
24502446
for (auto &desc : *Imports) {
24512447
// Ignore implementation-only imports.
2452-
if (desc.options.contains(ImportFlags::ImplementationOnly))
2448+
if (desc.options.contains(ImportFlags::ImplementationOnly)) {
2449+
if (imports.isImportedBy(module, desc.module.importedModule))
2450+
importKind = RestrictedImportKind::ImplementationOnly;
24532451
continue;
2452+
}
24542453

2455-
// If the module is imported this way, it's not imported
2454+
// If the module is imported publicly, it's not imported
24562455
// implementation-only.
24572456
if (imports.isImportedBy(module, desc.module.importedModule))
2458-
return false;
2457+
return RestrictedImportKind::None;
24592458
}
24602459

24612460
// Now check this file's enclosing module in case there are re-exports.
2462-
return !imports.isImportedBy(module, getParentModule());
2461+
if (imports.isImportedBy(module, getParentModule()))
2462+
return RestrictedImportKind::None;
2463+
2464+
return importKind;
24632465
}
24642466

24652467
bool ModuleDecl::isImportedImplementationOnly(const ModuleDecl *module) const {

lib/Sema/ResilienceDiagnostics.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,16 @@ TypeChecker::diagnoseDeclRefExportability(SourceLoc loc,
146146

147147
D->diagnose(diag::kind_declared_here, DescriptiveDeclKind::Type);
148148
} else {
149-
ctx.Diags.diagnose(loc, diag::inlinable_decl_ref_from_hidden_module,
149+
// Only implicitly imported decls should be reported as a warning,
150+
// and only for language versions below Swift 6.
151+
assert(downgradeToWarning == DowngradeToWarning::No ||
152+
originKind == DisallowedOriginKind::ImplicitlyImported &&
153+
"Only implicitly imported decls should be reported as a warning.");
154+
auto errorOrWarning = downgradeToWarning == DowngradeToWarning::Yes?
155+
diag::inlinable_decl_ref_from_hidden_module_warn:
156+
diag::inlinable_decl_ref_from_hidden_module;
157+
158+
ctx.Diags.diagnose(loc, errorOrWarning,
150159
D->getDescriptiveKind(), D->getName(),
151160
fragileKind.getSelector(), definingModule->getName(),
152161
static_cast<unsigned>(originKind));

lib/Sema/TypeCheckAccess.cpp

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,12 +1503,23 @@ swift::getDisallowedOriginKind(const Decl *decl,
15031503
downgradeToWarning = DowngradeToWarning::No;
15041504
ModuleDecl *M = decl->getModuleContext();
15051505
auto *SF = where.getDeclContext()->getParentSourceFile();
1506-
if (SF->isImportedImplementationOnly(M)) {
1506+
1507+
RestrictedImportKind howImported = SF->getRestrictedImportKind(M);
1508+
if (howImported != RestrictedImportKind::None) {
15071509
// Temporarily downgrade implementation-only exportability in SPI to
15081510
// a warning.
15091511
if (where.isSPI())
15101512
downgradeToWarning = DowngradeToWarning::Yes;
15111513

1514+
// Before Swift 6, implicit imports were not reported unless an
1515+
// implementation-only import was also present. Downgrade to a warning
1516+
// just in this case.
1517+
if (howImported == RestrictedImportKind::Implicit &&
1518+
!SF->getASTContext().isSwiftVersionAtLeast(6) &&
1519+
!SF->hasImplementationOnlyImports()) {
1520+
downgradeToWarning = DowngradeToWarning::Yes;
1521+
}
1522+
15121523
// Even if the current module is @_implementationOnly, Swift should
15131524
// not report an error in the cases where the decl is also exported from
15141525
// a non @_implementationOnly module. Thus, we check to see if there is
@@ -1529,9 +1540,12 @@ swift::getDisallowedOriginKind(const Decl *decl,
15291540
continue;
15301541
}
15311542
}
1543+
auto owningModule = redecl->getOwningModule();
1544+
if (!owningModule)
1545+
continue;
15321546
auto moduleWrapper =
15331547
decl->getASTContext().getClangModuleLoader()->getWrapperForModule(
1534-
redecl->getOwningModule());
1548+
owningModule);
15351549
auto visibleAccessPath =
15361550
find_if(sfImportedModules, [&moduleWrapper](auto importedModule) {
15371551
return importedModule.importedModule == moduleWrapper ||
@@ -1543,7 +1557,10 @@ swift::getDisallowedOriginKind(const Decl *decl,
15431557
}
15441558
}
15451559
}
1546-
// Implementation-only imported, cannot be reexported.
1560+
1561+
// Restrictively imported, cannot be reexported.
1562+
if (howImported == RestrictedImportKind::Implicit)
1563+
return DisallowedOriginKind::ImplicitlyImported;
15471564
return DisallowedOriginKind::ImplementationOnly;
15481565
} else if ((decl->isSPI() || decl->isAvailableAsSPI()) && !where.isSPI()) {
15491566
if (decl->isAvailableAsSPI() && !decl->isSPI()) {
@@ -1883,7 +1900,8 @@ class DeclAvailabilityChecker : public DeclVisitor<DeclAvailabilityChecker> {
18831900

18841901
const SourceFile *SF = refDecl->getDeclContext()->getParentSourceFile();
18851902
ModuleDecl *M = PGD->getModuleContext();
1886-
if (!SF->isImportedImplementationOnly(M))
1903+
RestrictedImportKind howImported = SF->getRestrictedImportKind(M);
1904+
if (howImported == RestrictedImportKind::None)
18871905
return;
18881906

18891907
auto &DE = PGD->getASTContext().Diags;

lib/Sema/TypeCheckAccess.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@ void checkAccessControl(Decl *D);
3535
// Problematic origin of an exported type.
3636
//
3737
// This enum must be kept in sync with
38+
// diag::inlinable_decl_ref_from_hidden_module,
3839
// diag::decl_from_hidden_module and
3940
// diag::conformance_from_implementation_only_module.
4041
enum class DisallowedOriginKind : uint8_t {
4142
ImplementationOnly,
4243
SPIImported,
4344
SPILocal,
45+
ImplicitlyImported,
4446
None
4547
};
4648

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2330,7 +2330,7 @@ void swift::checkImplementationOnlyOverride(const ValueDecl *VD) {
23302330
assert(SF && "checking a non-source declaration?");
23312331

23322332
ModuleDecl *M = overridden->getModuleContext();
2333-
if (SF->isImportedImplementationOnly(M)) {
2333+
if (SF->getRestrictedImportKind(M) == RestrictedImportKind::ImplementationOnly) {
23342334
VD->diagnose(diag::implementation_only_override_import_without_attr,
23352335
overridden->getDescriptiveKind())
23362336
.fixItInsert(VD->getAttributeInsertionLoc(false),

stdlib/private/OSLog/OSLogFloatingPointTypes.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
//
2121
// "\(x, format: .fixed(precision: 10), privacy: .private\)"
2222

23+
import ObjectiveC
24+
2325
extension OSLogInterpolation {
2426

2527
/// Defines interpolation for expressions of type Float.

stdlib/private/OSLog/OSLogIntegerTypes.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
// 1. "\(x, format: .hex, privacy: .private, align: .right\)"
2323
// 2. "\(x, format: .hex(minDigits: 10), align: .right(columns: 10)\)"
2424

25+
import ObjectiveC
26+
2527
extension OSLogInterpolation {
2628

2729
/// Defines interpolation for expressions of type Int.

stdlib/private/OSLog/OSLogStringTypes.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
// 1. "\(x, privacy: .private, align: .right\)"
2323
// 2. "\(x, align: .right(columns: 10)\)"
2424

25+
import ObjectiveC
26+
2527
extension OSLogInterpolation {
2628

2729
/// Defines interpolation for expressions of type String.

test/SPI/implementation_only_spi_import_exposability.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ public protocol IOIProtocol {}
2626

2727
@_spi(A) @_implementationOnly import Lib
2828

29-
@_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}}
30-
@_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}}
29+
@_spi(B) public func leakSPIStruct(_ a: SPIStruct) -> SPIStruct { fatalError() } // expected-warning 2 {{cannot use struct 'SPIStruct' here; 'Lib' has been imported as implementation-only}}
30+
@_spi(B) public func leakIOIStruct(_ a: IOIStruct) -> IOIStruct { fatalError() } // expected-warning 2 {{cannot use struct 'IOIStruct' here; 'Lib' has been imported as implementation-only}}
3131

3232
public struct PublicStruct : IOIProtocol, SPIProtocol { // expected-error {{cannot use protocol 'IOIProtocol' here; 'Lib' has been imported as implementation-only}}
3333
// 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
4646
}
4747

4848
@_spi(B)
49-
public struct LocalSPIStruct : IOIProtocol, SPIProtocol { // expected-warning {{cannot use protocol 'IOIProtocol' in SPI; 'Lib' has been imported as implementation-only}}
50-
// expected-warning @-1 {{cannot use protocol 'SPIProtocol' in SPI; 'Lib' has been imported as implementation-only}}
49+
public struct LocalSPIStruct : IOIProtocol, SPIProtocol { // expected-warning {{cannot use protocol 'IOIProtocol' here; 'Lib' has been imported as implementation-only}}
50+
// expected-warning @-1 {{cannot use protocol 'SPIProtocol' here; 'Lib' has been imported as implementation-only}}
5151
}
5252

5353
#endif
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/// Report the use in API of indirectly or implicitly imported decls.
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: %{python} %utils/split_file.py -o %t %s
5+
6+
// RUN: %target-swift-frontend -emit-module %t/empty.swift -module-name empty -o %t/empty.swiftmodule
7+
// RUN: %target-swift-frontend -emit-module %t/libA.swift -module-name libA -o %t/libA.swiftmodule
8+
// RUN: %target-swift-frontend -emit-module %t/libB.swift -module-name libB -o %t/libB.swiftmodule -I %t
9+
10+
/// In pre-Swift 6, this is a warning where there's no implementation-only import present.
11+
// RUN: %target-swift-frontend -emit-module %t/clientFileA-Swift5.swift %t/clientFileB.swift -module-name client -o %t/client.swiftmodule -I %t -verify
12+
13+
/// In pre-Swift 6, this remains an error when there's an implementation-only import present.
14+
// RUN: %target-swift-frontend -emit-module %t/clientFileA-OldCheck.swift %t/clientFileB.swift -module-name client -o %t/client.swiftmodule -I %t -verify
15+
16+
/// In Swift 6, it's an error.
17+
// 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
18+
19+
// BEGIN empty.swift
20+
21+
// BEGIN libA.swift
22+
public struct ImportedType {
23+
public init() {}
24+
}
25+
26+
// BEGIN libB.swift
27+
import libA
28+
29+
extension ImportedType {
30+
public func implicitlyImportedMethod() {}
31+
}
32+
33+
/// Client module
34+
// BEGIN clientFileA-Swift5.swift
35+
import libA
36+
37+
@inlinable public func bar() {
38+
let a = ImportedType()
39+
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}}
40+
41+
// Expected implicit imports are still fine
42+
a.localModuleMethod()
43+
}
44+
45+
// BEGIN clientFileA-OldCheck.swift
46+
import libA
47+
@_implementationOnly import empty
48+
49+
@inlinable public func bar() {
50+
let a = ImportedType()
51+
a.implicitlyImportedMethod() // expected-error {{instance method 'implicitlyImportedMethod()' cannot be used in an '@inlinable' function because 'libB' was not imported by this file}}
52+
53+
// Expected implicit imports are still fine
54+
a.localModuleMethod()
55+
}
56+
57+
// BEGIN clientFileA-Swift6.swift
58+
import libA
59+
60+
@inlinable public func bar() {
61+
let a = ImportedType()
62+
a.implicitlyImportedMethod() // expected-error {{instance method 'implicitlyImportedMethod()' cannot be used in an '@inlinable' function because 'libB' was not imported by this file}}
63+
64+
// Expected implicit imports are still fine
65+
a.localModuleMethod()
66+
}
67+
68+
// BEGIN clientFileB.swift
69+
@_implementationOnly import libB
70+
import libA
71+
extension ImportedType {
72+
public func localModuleMethod() {}
73+
}
74+

0 commit comments

Comments
 (0)