Skip to content

Commit 6d396b4

Browse files
committed
[Sema] Check @_spiOnly imported decls exportability
Decls imported via an @_spiOnly import can only be used in @_spi decls signatures, internal decl signatures and non-inlinable function bodies.
1 parent 67e35c3 commit 6d396b4

File tree

8 files changed

+308
-12
lines changed

8 files changed

+308
-12
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2931,6 +2931,7 @@ ERROR(decl_from_hidden_module,none,
29312931
"%select{%3 has been imported as implementation-only|"
29322932
"it is an SPI imported from %3|"
29332933
"it is SPI|"
2934+
"%3 was imported for SPI only|"
29342935
"%3 was not imported by this file}4",
29352936
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
29362937
WARNING(decl_from_hidden_module_warn,none,
@@ -2939,7 +2940,7 @@ WARNING(decl_from_hidden_module_warn,none,
29392940
"in an extension with public or '@usableFromInline' members|"
29402941
"in an extension with conditional conformances}2; "
29412942
"%select{%3 has been imported as implementation-only|"
2942-
"<<ERROR>>|<<ERROR>>|"
2943+
"<<ERROR>>|<<ERROR>>|<<ERROR>>|"
29432944
"%3 was not imported by this file}4",
29442945
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
29452946
ERROR(typealias_desugars_to_type_from_hidden_module,none,
@@ -2951,6 +2952,7 @@ ERROR(typealias_desugars_to_type_from_hidden_module,none,
29512952
"because %select{%4 has been imported as implementation-only|"
29522953
"it is an SPI imported from %4|"
29532954
"<<ERROR>>|"
2955+
"%4 was imported for SPI only|"
29542956
"%4 was not imported by this file}5",
29552957
(DeclName, StringRef, StringRef, unsigned, Identifier, unsigned))
29562958
ERROR(conformance_from_implementation_only_module,none,
@@ -2961,6 +2963,7 @@ ERROR(conformance_from_implementation_only_module,none,
29612963
"%select{%3 has been imported as implementation-only|"
29622964
"the conformance is declared as SPI in %3|"
29632965
"the conformance is declared as SPI|"
2966+
"%3 was imported for SPI only|"
29642967
"%3 was not imported by this file}4",
29652968
(Type, Identifier, unsigned, Identifier, unsigned))
29662969
NOTE(assoc_conformance_from_implementation_only_module,none,
@@ -5860,12 +5863,13 @@ ERROR(inlinable_decl_ref_from_hidden_module,
58605863
"because %select{%3 was imported implementation-only|"
58615864
"it is an SPI imported from %3|"
58625865
"it is SPI|"
5866+
"%3 was imported for SPI only|"
58635867
"%3 was not imported by this file}4",
58645868
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
58655869

58665870
WARNING(inlinable_decl_ref_from_hidden_module_warn,
58675871
none, "%0 %1 cannot be used in " FRAGILE_FUNC_KIND "2 "
5868-
"because %select{<<ERROR>>|<<ERROR>>|<<ERROR>>|"
5872+
"because %select{<<ERROR>>|<<ERROR>>|<<ERROR>>|<<ERROR>>|"
58695873
"%3 was not imported by this file}4"
58705874
"; this is an error in Swift 6",
58715875
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
@@ -5875,6 +5879,7 @@ ERROR(inlinable_typealias_desugars_to_type_from_hidden_module,
58755879
"because %select{%4 has been imported as implementation-only|"
58765880
"it is an SPI imported from %4|"
58775881
"<<ERROR>>|"
5882+
"%4 was imported for SPI only|"
58785883
"%4 was not imported by this file}5",
58795884
(DeclName, StringRef, StringRef, unsigned, Identifier, unsigned))
58805885

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
@@ -2527,21 +2527,25 @@ RestrictedImportKind SourceFile::getRestrictedImportKind(const ModuleDecl *modul
25272527
auto &imports = getASTContext().getImportCache();
25282528
RestrictedImportKind importKind = RestrictedImportKind::Implicit;
25292529

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

25332535
// Look at the imports of this source file.
25342536
for (auto &desc : *Imports) {
2535-
// Ignore implementation-only imports.
25362537
if (desc.options.contains(ImportFlags::ImplementationOnly)) {
2537-
if (imports.isImportedBy(module, desc.module.importedModule))
2538+
if (importKind < RestrictedImportKind::ImplementationOnly &&
2539+
imports.isImportedBy(module, desc.module.importedModule))
25382540
importKind = RestrictedImportKind::ImplementationOnly;
2539-
continue;
25402541
}
2541-
2542-
// If the module is imported publicly, it's not imported
2543-
// implementation-only.
2544-
if (imports.isImportedBy(module, desc.module.importedModule))
2542+
else if (desc.options.contains(ImportFlags::SPIOnly)) {
2543+
if (importKind < RestrictedImportKind::SPIOnly &&
2544+
imports.isImportedBy(module, desc.module.importedModule))
2545+
importKind = RestrictedImportKind::SPIOnly;
2546+
}
2547+
// If the module is imported publicly, there's no restriction.
2548+
else if (imports.isImportedBy(module, desc.module.importedModule))
25452549
return RestrictedImportKind::None;
25462550
}
25472551

lib/Sema/TypeCheckAccess.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1551,6 +1551,9 @@ swift::getDisallowedOriginKind(const Decl *decl,
15511551
if (where.isSPI())
15521552
downgradeToWarning = DowngradeToWarning::Yes;
15531553

1554+
if (where.isSPI() && howImported == RestrictedImportKind::SPIOnly)
1555+
return DisallowedOriginKind::None;
1556+
15541557
// Before Swift 6, implicit imports were not reported unless an
15551558
// implementation-only import was also present. Downgrade to a warning
15561559
// just in this case.
@@ -1599,9 +1602,16 @@ swift::getDisallowedOriginKind(const Decl *decl,
15991602
}
16001603

16011604
// Restrictively imported, cannot be reexported.
1602-
if (howImported == RestrictedImportKind::Implicit)
1605+
switch (howImported) {
1606+
case RestrictedImportKind::Implicit:
16031607
return DisallowedOriginKind::ImplicitlyImported;
1604-
return DisallowedOriginKind::ImplementationOnly;
1608+
case RestrictedImportKind::SPIOnly:
1609+
return DisallowedOriginKind::SPIOnly;
1610+
case RestrictedImportKind::ImplementationOnly:
1611+
return DisallowedOriginKind::ImplementationOnly;
1612+
default:
1613+
llvm_unreachable("RestrictedImportKind isn't handled");
1614+
}
16051615
} else if ((decl->isSPI() || decl->isAvailableAsSPI()) && !where.isSPI()) {
16061616
if (decl->isAvailableAsSPI() && !decl->isSPI()) {
16071617
// 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
};
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() }
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/// Test @_spiOnly exportability type-checking
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: split-file %s %t
5+
6+
/// Generate dependencies.
7+
// RUN: %target-swift-frontend -emit-module %t/PublicLib.swift \
8+
// RUN: -module-name PublicLib -emit-module-path %t/PublicLib.swiftmodule \
9+
// RUN: -swift-version 5 -enable-library-evolution
10+
// RUN: %target-swift-frontend -emit-module %t/SPIOnlyImportedLib.swift \
11+
// RUN: -module-name SPIOnlyImportedLib \
12+
// RUN: -emit-module-path %t/SPIOnlyImportedLib.swiftmodule \
13+
// RUN: -swift-version 5 -enable-library-evolution -I %t
14+
15+
/// Test the client.
16+
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t -verify \
17+
// RUN: -experimental-spi-only-imports
18+
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t -verify \
19+
// RUN: -experimental-spi-only-imports \
20+
// RUN: -enable-library-evolution
21+
22+
/// Generate the swiftinterface of the working code and verify it.
23+
// RUN: %target-swift-emit-module-interface(%t/Client.swiftinterface) \
24+
// RUN: %t/Client.swift -I %t -DSKIP_ERRORS \
25+
// RUN: -experimental-spi-only-imports
26+
// RUN: %target-swift-typecheck-module-from-interface(%t/Client.swiftinterface) -I %t
27+
28+
29+
//--- PublicLib.swift
30+
31+
public struct PublicType {
32+
public init() {}
33+
}
34+
35+
public protocol PublicProtocol {}
36+
public func conformanceUse(_ a: PublicProtocol) {}
37+
38+
@propertyWrapper
39+
public struct PublicPropertyWrapper<T> {
40+
public var wrappedValue: T
41+
42+
public init(wrappedValue value: T) { self.wrappedValue = value }
43+
public init(_ value: T) { self.wrappedValue = value }
44+
}
45+
46+
//--- SPIOnlyImportedLib.swift
47+
import PublicLib
48+
49+
public func spiOnlyFunc() -> String { fatalError() }
50+
51+
public protocol SPIOnlyProto {
52+
}
53+
54+
public struct SPIOnlyStruct {
55+
public init() {}
56+
public func structMethod() {}
57+
}
58+
59+
public class SPIOnlyClass<P: PublicProtocol> {
60+
public init(p: P) {}
61+
public func classMethod() {}
62+
}
63+
64+
public enum SPIOnlyEnum {
65+
case A
66+
case B
67+
public func enumMethod() {}
68+
}
69+
70+
extension PublicType {
71+
public func spiOnlyExtensionMethod() {}
72+
}
73+
74+
extension PublicType : PublicProtocol {}
75+
76+
@propertyWrapper
77+
public struct SPIOnlyPropertyWrapper<T> {
78+
public var wrappedValue: T
79+
80+
public init(wrappedValue value: T) { self.wrappedValue = value }
81+
public init(_ value: T) { self.wrappedValue = value }
82+
}
83+
84+
//--- Client.swift
85+
86+
import PublicLib
87+
@_spiOnly import SPIOnlyImportedLib
88+
89+
#if !SKIP_ERRORS
90+
public func publicUser<T: SPIOnlyProto>(_ a: SPIOnlyStruct, t: T) -> SPIOnlyStruct { fatalError() }
91+
// expected-error @-1 2 {{cannot use struct 'SPIOnlyStruct' here; 'SPIOnlyImportedLib' was imported for SPI only}}
92+
// expected-error @-2 {{cannot use protocol 'SPIOnlyProto' here; 'SPIOnlyImportedLib' was imported for SPI only}}
93+
#endif
94+
95+
@_spi(X) public func spiUser(_ a: SPIOnlyStruct) -> SPIOnlyStruct { fatalError() }
96+
97+
internal func internalUser(_ a: SPIOnlyStruct) -> SPIOnlyStruct { fatalError() }
98+
99+
#if !SKIP_ERRORS
100+
@inlinable
101+
public func publicInlinableUser() {
102+
_ = spiOnlyFunc() // expected-error {{global function 'spiOnlyFunc()' cannot be used in an '@inlinable' function because 'SPIOnlyImportedLib' was imported for SPI only}}
103+
104+
var x: SPIOnlyStruct // expected-error {{struct 'SPIOnlyStruct' cannot be used in an '@inlinable' function because 'SPIOnlyImportedLib' was imported for SPI only}}
105+
x = SPIOnlyStruct() // expected-error {{struct 'SPIOnlyStruct' cannot be used in an '@inlinable' function because 'SPIOnlyImportedLib' was imported for SPI only}}
106+
// expected-error @-1 {{initializer 'init()' cannot be used in an '@inlinable' function because 'SPIOnlyImportedLib' was imported for SPI only}}
107+
x.structMethod() // expected-error {{instance method 'structMethod()' cannot be used in an '@inlinable' function because 'SPIOnlyImportedLib' was imported for SPI only}}
108+
109+
var c: SPIOnlyClass<PublicType> // expected-error {{generic class 'SPIOnlyClass' cannot be used in an '@inlinable' function because 'SPIOnlyImportedLib' was imported for SPI only}}
110+
c = SPIOnlyClass(p: PublicType()) // expected-error {{generic class 'SPIOnlyClass' cannot be used in an '@inlinable' function because 'SPIOnlyImportedLib' was imported for SPI only}}
111+
// expected-error @-1 {{initializer 'init(p:)' cannot be used in an '@inlinable' function because 'SPIOnlyImportedLib' was imported for SPI only}}
112+
c.classMethod() // expected-error {{instance method 'classMethod()' cannot be used in an '@inlinable' function because 'SPIOnlyImportedLib' was imported for SPI only}}
113+
114+
var e: SPIOnlyEnum // expected-error {{enum 'SPIOnlyEnum' cannot be used in an '@inlinable' function because 'SPIOnlyImportedLib' was imported for SPI only}}
115+
e = .A // expected-error {{enum case 'A' cannot be used in an '@inlinable' function because 'SPIOnlyImportedLib' was imported for SPI only}}
116+
e.enumMethod() // expected-error {{instance method 'enumMethod()' cannot be used in an '@inlinable' function because 'SPIOnlyImportedLib' was imported for SPI only}}
117+
118+
let p: PublicType = PublicType()
119+
p.spiOnlyExtensionMethod() // expected-error {{instance method 'spiOnlyExtensionMethod()' cannot be used in an '@inlinable' function because 'SPIOnlyImportedLib' was imported for SPI only}}
120+
}
121+
#endif
122+
123+
@inlinable @_spi(X)
124+
public func spiInlinableUser() {
125+
_ = spiOnlyFunc()
126+
127+
var x: SPIOnlyStruct
128+
x = SPIOnlyStruct()
129+
x.structMethod()
130+
131+
let p: PublicType = PublicType()
132+
p.spiOnlyExtensionMethod()
133+
}
134+
135+
public func implementationDetailsUser() {
136+
_ = spiOnlyFunc()
137+
138+
var x: SPIOnlyStruct
139+
x = SPIOnlyStruct()
140+
x.structMethod()
141+
142+
let p: PublicType = PublicType()
143+
p.spiOnlyExtensionMethod()
144+
}
145+
146+
public struct ClientStruct {
147+
#if !SKIP_ERRORS
148+
public var a: SPIOnlyStruct // expected-error {{cannot use struct 'SPIOnlyStruct' here; 'SPIOnlyImportedLib' was imported for SPI only}}
149+
@SPIOnlyPropertyWrapper(42) public var aWrapped: Any // expected-error {{cannot use generic struct 'SPIOnlyPropertyWrapper' as property wrapper here; 'SPIOnlyImportedLib' was imported for SPI only}}
150+
#endif
151+
@PublicPropertyWrapper(SPIOnlyStruct()) public var bWrapped: Any
152+
}

0 commit comments

Comments
 (0)