Skip to content

Commit 7b9d4e6

Browse files
authored
Merge pull request #61011 from xymus/spi-only-exportability
[Sema] Check exportability of @_spiOnly imported decls
2 parents 11df615 + d1e36b0 commit 7b9d4e6

11 files changed

+380
-13
lines changed

docs/Testing.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,12 @@ code for the target that is not the build machine:
344344
swiftinterface to the given path and passing additional default flags
345345
appropriate for resilient frameworks.
346346

347+
* ``%target-swift-emit-module-interfaces(`` *swift interface path*,
348+
*swift private interface path* ``)`` *other arguments*:
349+
run ``swift-frontend`` for the target, emitting both swiftinterfaces
350+
to the given paths and passing additional default flags appropriate for
351+
resilient frameworks.
352+
347353
* ``%target-swift-typecheck-module-from-interface(`` *swift interface path*
348354
``)`` *other arguments*: run ``swift-frontend`` for the target, verifying
349355
the swiftinterface at the given path and passing additional default flags

include/swift/AST/DiagnosticsSema.def

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2952,6 +2952,7 @@ ERROR(decl_from_hidden_module,none,
29522952
"%select{%3 has been imported as implementation-only|"
29532953
"it is an SPI imported from %3|"
29542954
"it is SPI|"
2955+
"%3 was imported for SPI only|"
29552956
"%3 was not imported by this file}4",
29562957
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
29572958
WARNING(decl_from_hidden_module_warn,none,
@@ -2960,7 +2961,7 @@ WARNING(decl_from_hidden_module_warn,none,
29602961
"in an extension with public or '@usableFromInline' members|"
29612962
"in an extension with conditional conformances}2; "
29622963
"%select{%3 has been imported as implementation-only|"
2963-
"<<ERROR>>|<<ERROR>>|"
2964+
"<<ERROR>>|<<ERROR>>|<<ERROR>>|"
29642965
"%3 was not imported by this file}4",
29652966
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
29662967
ERROR(typealias_desugars_to_type_from_hidden_module,none,
@@ -2972,6 +2973,7 @@ ERROR(typealias_desugars_to_type_from_hidden_module,none,
29722973
"because %select{%4 has been imported as implementation-only|"
29732974
"it is an SPI imported from %4|"
29742975
"<<ERROR>>|"
2976+
"%4 was imported for SPI only|"
29752977
"%4 was not imported by this file}5",
29762978
(DeclName, StringRef, StringRef, unsigned, Identifier, unsigned))
29772979
ERROR(conformance_from_implementation_only_module,none,
@@ -2982,6 +2984,7 @@ ERROR(conformance_from_implementation_only_module,none,
29822984
"%select{%3 has been imported as implementation-only|"
29832985
"the conformance is declared as SPI in %3|"
29842986
"the conformance is declared as SPI|"
2987+
"%3 was imported for SPI only|"
29852988
"%3 was not imported by this file}4",
29862989
(Type, Identifier, unsigned, Identifier, unsigned))
29872990
NOTE(assoc_conformance_from_implementation_only_module,none,
@@ -5892,12 +5895,13 @@ ERROR(inlinable_decl_ref_from_hidden_module,
58925895
"because %select{%3 was imported implementation-only|"
58935896
"it is an SPI imported from %3|"
58945897
"it is SPI|"
5898+
"%3 was imported for SPI only|"
58955899
"%3 was not imported by this file}4",
58965900
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
58975901

58985902
WARNING(inlinable_decl_ref_from_hidden_module_warn,
58995903
none, "%0 %1 cannot be used in " FRAGILE_FUNC_KIND "2 "
5900-
"because %select{<<ERROR>>|<<ERROR>>|<<ERROR>>|"
5904+
"because %select{<<ERROR>>|<<ERROR>>|<<ERROR>>|<<ERROR>>|"
59015905
"%3 was not imported by this file}4"
59025906
"; this is an error in Swift 6",
59035907
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
@@ -5907,6 +5911,7 @@ ERROR(inlinable_typealias_desugars_to_type_from_hidden_module,
59075911
"because %select{%4 has been imported as implementation-only|"
59085912
"it is an SPI imported from %4|"
59095913
"<<ERROR>>|"
5914+
"%4 was imported for SPI only|"
59105915
"%4 was not imported by this file}5",
59115916
(DeclName, StringRef, StringRef, unsigned, Identifier, unsigned))
59125917

include/swift/AST/SourceFile.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,20 @@ namespace swift {
2626
class PersistentParserState;
2727

2828
/// Kind of import affecting how a decl can be reexported.
29+
///
30+
/// This is sorted in order of priority in case the same module is imported
31+
/// differently. e.g. a normal import (None) offers more visibility than
32+
/// an @_spiOnly import, which offers more visibility than an
33+
/// @_implementationOnly import. The logic of \c getRestrictedImportKind relies
34+
/// on the order of this enum.
35+
///
2936
/// This is a subset of \c DisallowedOriginKind.
3037
///
3138
/// \sa getRestrictedImportKind
3239
enum class RestrictedImportKind {
33-
ImplementationOnly,
3440
Implicit,
41+
ImplementationOnly,
42+
SPIOnly,
3543
None // No restriction, i.e. the module is imported publicly.
3644
};
3745

lib/AST/Module.cpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2529,21 +2529,25 @@ RestrictedImportKind SourceFile::getRestrictedImportKind(const ModuleDecl *modul
25292529
auto &imports = getASTContext().getImportCache();
25302530
RestrictedImportKind importKind = RestrictedImportKind::Implicit;
25312531

2532+
// Workaround for the cases where the bridging header isn't properly
2533+
// imported implicitly.
25322534
if (module->getName().str() == CLANG_HEADER_MODULE_NAME)
25332535
return RestrictedImportKind::None;
25342536

25352537
// Look at the imports of this source file.
25362538
for (auto &desc : *Imports) {
2537-
// Ignore implementation-only imports.
25382539
if (desc.options.contains(ImportFlags::ImplementationOnly)) {
2539-
if (imports.isImportedBy(module, desc.module.importedModule))
2540+
if (importKind < RestrictedImportKind::ImplementationOnly &&
2541+
imports.isImportedBy(module, desc.module.importedModule))
25402542
importKind = RestrictedImportKind::ImplementationOnly;
2541-
continue;
25422543
}
2543-
2544-
// If the module is imported publicly, it's not imported
2545-
// implementation-only.
2546-
if (imports.isImportedBy(module, desc.module.importedModule))
2544+
else if (desc.options.contains(ImportFlags::SPIOnly)) {
2545+
if (importKind < RestrictedImportKind::SPIOnly &&
2546+
imports.isImportedBy(module, desc.module.importedModule))
2547+
importKind = RestrictedImportKind::SPIOnly;
2548+
}
2549+
// If the module is imported publicly, there's no restriction.
2550+
else if (imports.isImportedBy(module, desc.module.importedModule))
25472551
return RestrictedImportKind::None;
25482552
}
25492553

lib/Sema/TypeCheckAccess.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,9 +1548,14 @@ swift::getDisallowedOriginKind(const Decl *decl,
15481548
if (howImported != RestrictedImportKind::None) {
15491549
// Temporarily downgrade implementation-only exportability in SPI to
15501550
// a warning.
1551-
if (where.isSPI())
1551+
if (where.isSPI() &&
1552+
where.getFragileFunctionKind().kind == FragileFunctionKind::None &&
1553+
!SF->getASTContext().LangOpts.EnableSPIOnlyImports)
15521554
downgradeToWarning = DowngradeToWarning::Yes;
15531555

1556+
if (where.isSPI() && howImported == RestrictedImportKind::SPIOnly)
1557+
return DisallowedOriginKind::None;
1558+
15541559
// Before Swift 6, implicit imports were not reported unless an
15551560
// implementation-only import was also present. Downgrade to a warning
15561561
// just in this case.
@@ -1599,9 +1604,16 @@ swift::getDisallowedOriginKind(const Decl *decl,
15991604
}
16001605

16011606
// Restrictively imported, cannot be reexported.
1602-
if (howImported == RestrictedImportKind::Implicit)
1607+
switch (howImported) {
1608+
case RestrictedImportKind::Implicit:
16031609
return DisallowedOriginKind::ImplicitlyImported;
1604-
return DisallowedOriginKind::ImplementationOnly;
1610+
case RestrictedImportKind::SPIOnly:
1611+
return DisallowedOriginKind::SPIOnly;
1612+
case RestrictedImportKind::ImplementationOnly:
1613+
return DisallowedOriginKind::ImplementationOnly;
1614+
default:
1615+
llvm_unreachable("RestrictedImportKind isn't handled");
1616+
}
16051617
} else if ((decl->isSPI() || decl->isAvailableAsSPI()) && !where.isSPI()) {
16061618
if (decl->isAvailableAsSPI() && !decl->isSPI()) {
16071619
// Allowing unavailable context to use @_spi_available decls.

lib/Sema/TypeCheckAccess.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ enum class DisallowedOriginKind : uint8_t {
4444
ImplementationOnly,
4545
SPIImported,
4646
SPILocal,
47+
SPIOnly,
4748
ImplicitlyImported,
4849
None
4950
};

test/SPI/report-ioi-in-spi.swift

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: split-file %s %t
3+
4+
// RUN: %target-swift-frontend -emit-module %t/Lib.swift -I %t \
5+
// RUN: -module-name Lib -emit-module-path %t/Lib.swiftmodule \
6+
// RUN: -swift-version 5
7+
8+
/// Use of IOI types in SPI signatures is an error with -experimental-spi-only-imports
9+
// RUN: %target-swift-frontend -emit-module %t/ClientSPIOnlyMode.swift -I %t \
10+
// RUN: -swift-version 5 -verify \
11+
// RUN: -experimental-spi-only-imports
12+
13+
/// Use of IOI types in SPI signatures is a warning without -experimental-spi-only-imports
14+
// RUN: %target-swift-frontend -emit-module %t/ClientDefaultMode.swift -I %t \
15+
// RUN: -swift-version 5 -verify
16+
17+
/// This is a warning in swiftinterfaces
18+
// R UN: %target-swift-typecheck-module-from-interface(%t/Client.private.swiftinterface) \
19+
// R UN: -I %t -module-name Client
20+
21+
//--- Lib.swift
22+
23+
public struct IOIStruct {
24+
public init() {}
25+
}
26+
27+
//--- ClientSPIOnlyMode.swift
28+
29+
@_implementationOnly import Lib
30+
31+
@_spi(X) public func spiClient(s: IOIStruct) -> IOIStruct { // expected-error 2 {{cannot use struct 'IOIStruct' here; 'Lib' has been imported as implementation-only}}
32+
return IOIStruct()
33+
}
34+
35+
@_spi(X) @inlinable public func inlinableClient(s: IOIStruct) -> IOIStruct { // expected-error 2 {{struct 'IOIStruct' cannot be used in an '@inlinable' function because 'Lib' was imported implementation-only}}
36+
return IOIStruct() // expected-error {{struct 'IOIStruct' cannot be used in an '@inlinable' function because 'Lib' was imported implementation-only}}
37+
// expected-error @-1 {{initializer 'init()' cannot be used in an '@inlinable' function because 'Lib' was imported implementation-only}}
38+
}
39+
40+
//--- ClientDefaultMode.swift
41+
42+
@_implementationOnly import Lib
43+
44+
@_spi(X) public func spiClient(s: IOIStruct) -> IOIStruct { // expected-warning 2 {{cannot use struct 'IOIStruct' here; 'Lib' has been imported as implementation-only}}
45+
return IOIStruct()
46+
}
47+
48+
@_spi(X) @inlinable public func inlinableClient(s: IOIStruct) -> IOIStruct { // expected-error 2 {{struct 'IOIStruct' cannot be used in an '@inlinable' function because 'Lib' was imported implementation-only}}
49+
return IOIStruct() // expected-error {{struct 'IOIStruct' cannot be used in an '@inlinable' function because 'Lib' was imported implementation-only}}
50+
// expected-error @-1 {{initializer 'init()' cannot be used in an '@inlinable' function because 'Lib' was imported implementation-only}}
51+
}
52+
53+
//--- Client.private.swiftinterface
54+
55+
// swift-interface-format-version: 1.0
56+
// swift-compiler-version: Swift version 5.8-dev effective-4.1.50
57+
// swift-module-flags: -swift-version 4 -module-name Client
58+
@_implementationOnly import Lib
59+
60+
@_spi(X) public func spiClient() -> IOIStruct { fatalError() }
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: split-file %s %t
3+
4+
// RUN: %target-swift-frontend -emit-module %t/Lib.swift -I %t \
5+
// RUN: -module-name Lib -emit-module-path %t/Lib.swiftmodule \
6+
// RUN: -swift-version 5
7+
// RUN: %target-swift-frontend -emit-module %t/APILib.swift -I %t \
8+
// RUN: -swift-version 5 -verify \
9+
// RUN: -experimental-spi-only-imports \
10+
// RUN: -library-level api
11+
// RUN: %target-swift-frontend -emit-module %t/SPILib.swift -I %t \
12+
// RUN: -swift-version 5 -verify \
13+
// RUN: -experimental-spi-only-imports \
14+
// RUN: -library-level spi
15+
// RUN: %target-swift-frontend -emit-module %t/OtherLib.swift -I %t \
16+
// RUN: -swift-version 5 -verify \
17+
// RUN: -experimental-spi-only-imports
18+
19+
//--- Lib.swift
20+
21+
public struct LibStruct {}
22+
23+
//--- APILib.swift
24+
25+
@_spiOnly import Lib
26+
27+
public func publicClient() -> LibStruct { fatalError() } // expected-error {{cannot use struct 'LibStruct' here; 'Lib' was imported for SPI only}}
28+
@_spi(X) public func spiClient() -> LibStruct { fatalError() }
29+
30+
//--- SPILib.swift
31+
32+
@_spiOnly import Lib
33+
34+
public func publicClient() -> LibStruct { fatalError() }
35+
@_spi(X) public func spiClient() -> LibStruct { fatalError() }
36+
37+
//--- OtherLib.swift
38+
39+
@_spiOnly import Lib
40+
41+
public func publicClient() -> LibStruct { fatalError() } // expected-error {{cannot use struct 'LibStruct' here; 'Lib' was imported for SPI only}}
42+
@_spi(X) public func spiClient() -> LibStruct { fatalError() }

0 commit comments

Comments
 (0)