Skip to content

Commit 25e36ad

Browse files
authored
Merge pull request #62300 from tshortli/spi-enum-cases
ModuleInterface: Avoid printing `@_spi` enum elements in public swiftinterfaces
2 parents e545826 + 85ddda1 commit 25e36ad

14 files changed

+170
-52
lines changed

include/swift/AST/Decl.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7479,7 +7479,16 @@ class EnumCaseDecl final : public Decl,
74797479
Bits.EnumCaseDecl.NumElements};
74807480
}
74817481
SourceRange getSourceRange() const;
7482-
7482+
7483+
/// Returns the first of the member elements or null if there are no elements.
7484+
/// The attributes written with an EnumCaseDecl will be attached to each of
7485+
/// the elements instead so inspecting the attributes of the first element is
7486+
/// often useful.
7487+
EnumElementDecl *getFirstElement() const {
7488+
auto elements = getElements();
7489+
return elements.empty() ? nullptr : elements.front();
7490+
}
7491+
74837492
static bool classof(const Decl *D) {
74847493
return D->getKind() == DeclKind::EnumCase;
74857494
}

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1970,6 +1970,9 @@ ERROR(spi_attribute_on_protocol_requirement,none,
19701970
ERROR(spi_attribute_on_frozen_stored_properties,none,
19711971
"stored property %0 cannot be declared '@_spi' in a '@frozen' struct",
19721972
(DeclName))
1973+
ERROR(spi_attribute_on_frozen_enum_case,none,
1974+
"%0 %1 cannot be declared '@_spi' in a '@frozen' enum",
1975+
(DescriptiveDeclKind, DeclName))
19731976
WARNING(spi_attribute_on_import_of_public_module,none,
19741977
"'@_spi' import of %0 will not include any SPI symbols; "
19751978
"%0 was built from the public interface at %1",

lib/AST/ASTPrinter.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,18 @@ PrintOptions PrintOptions::printSwiftInterfaceFile(ModuleDecl *ModuleToPrint,
276276
return false;
277277
}
278278

279+
// Skip enum cases containing enum elements we wouldn't print.
280+
if (auto *ECD = dyn_cast<EnumCaseDecl>(D)) {
281+
if (auto *element = ECD->getFirstElement()) {
282+
// Enum elements are usually not printed, so we have to override the
283+
// print option controlling that.
284+
PrintOptions optionsCopy = options;
285+
optionsCopy.ExplodeEnumCaseDecls = true;
286+
if (!shouldPrint(element, optionsCopy))
287+
return false;
288+
}
289+
}
290+
279291
return ShouldPrintChecker::shouldPrint(D, options);
280292
}
281293
};
@@ -4204,14 +4216,14 @@ void PrintAST::printEnumElement(EnumElementDecl *elt) {
42044216
}
42054217

42064218
void PrintAST::visitEnumCaseDecl(EnumCaseDecl *decl) {
4207-
auto elems = decl->getElements();
4208-
if (!elems.empty()) {
4219+
if (auto *element = decl->getFirstElement()) {
42094220
// Documentation comments over the case are attached to the enum elements.
4210-
printDocumentationComment(elems[0]);
4211-
printAttributes(elems[0]);
4221+
printDocumentationComment(element);
4222+
printAttributes(element);
42124223
}
42134224
Printer.printIntroducerKeyword("case", Options, " ");
42144225

4226+
auto elems = decl->getElements();
42154227
llvm::interleave(elems.begin(), elems.end(),
42164228
[&](EnumElementDecl *elt) {
42174229
printEnumElement(elt);

lib/IDE/SyntaxModel.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,8 +1077,8 @@ ASTWalker::PreWalkAction ModelASTWalker::walkToDeclPre(Decl *D) {
10771077

10781078
// We need to handle the special case where attributes semantically
10791079
// attach to enum element decls while syntactically locate before enum case decl.
1080-
if (!EnumCaseD->getElements().empty()) {
1081-
if (!handleAttrs(EnumCaseD->getElements().front()->getAttrs()))
1080+
if (auto *element = EnumCaseD->getFirstElement()) {
1081+
if (!handleAttrs(element->getAttrs()))
10821082
return Action::SkipChildren();
10831083
}
10841084
if (pushStructureNode(SN, D)) {

lib/Sema/TypeCheckAttr.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,6 +1150,17 @@ void AttributeChecker::visitSPIAccessControlAttr(SPIAccessControlAttr *attr) {
11501150
}
11511151
}
11521152
}
1153+
1154+
// Forbid enum elements marked SPI in frozen types.
1155+
if (auto elt = dyn_cast<EnumElementDecl>(VD)) {
1156+
if (auto ED = dyn_cast<EnumDecl>(D->getDeclContext())) {
1157+
if (ED->getAttrs().hasAttribute<FrozenAttr>(/*allowInvalid*/ true) &&
1158+
!ED->isSPI()) {
1159+
diagnoseAndRemoveAttr(attr, diag::spi_attribute_on_frozen_enum_case,
1160+
VD->getDescriptiveKind(), VD->getName());
1161+
}
1162+
}
1163+
}
11531164
}
11541165

11551166
if (auto ID = dyn_cast<ImportDecl>(D)) {

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1609,9 +1609,8 @@ abstractSyntaxDeclForAvailableAttribute(const Decl *ConcreteSyntaxDecl) {
16091609
} else if (auto *ECD = dyn_cast<EnumCaseDecl>(ConcreteSyntaxDecl)) {
16101610
// Similar to the PatternBindingDecl case above, we return the
16111611
// first EnumElementDecl.
1612-
ArrayRef<EnumElementDecl *> Elems = ECD->getElements();
1613-
if (!Elems.empty()) {
1614-
return Elems.front();
1612+
if (auto *Elem = ECD->getFirstElement()) {
1613+
return Elem;
16151614
}
16161615
}
16171616

test/SPI/Inputs/spi_helper.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,8 @@ public struct PublicStruct {
8989
@_spi(HelperSPI) public struct IntLike: ExpressibleByIntegerLiteral, Equatable {
9090
@_spi(HelperSPI) public init(integerLiteral: Int) {}
9191
}
92+
93+
public enum PublicEnum {
94+
case publicCase
95+
@_spi(HelperSPI) case spiCase
96+
}

test/SPI/export_spi_from_spi_module.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,32 @@
1111
public init() {}
1212
}
1313
@_spi(S) public protocol SPIProtocol {}
14+
public enum PublicEnum {
15+
case publicCase
16+
@_spi(S) case spiCase
17+
}
1418

1519
public func useOfSPITypeOk(_ p0: SPIProtocol, p1: SPIClass) -> SPIClass { fatalError() }
1620

1721
@inlinable
18-
func inlinable() -> SPIClass {
22+
public func inlinable() -> SPIClass {
1923
spiFunc()
2024
_ = SPIClass()
2125
}
2226

27+
@inlinable
28+
public func inlinable(_ e: PublicEnum) {
29+
switch e {
30+
case .publicCase: break
31+
case .spiCase: break
32+
@unknown default: break
33+
}
34+
35+
if case .spiCase = e {}
36+
37+
_ = PublicEnum.spiCase
38+
}
39+
2340
@frozen public struct FrozenStruct {
2441
public var spiInFrozen = SPIStruct()
2542
var spiTypeInFrozen = SPIStruct()

test/SPI/private_swiftinterface.swift

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,6 @@
3434
// RUN: %target-swift-frontend -typecheck-module-from-interface -I %t %t/Main.swiftinterface
3535
// RUN: %target-swift-frontend -typecheck-module-from-interface -I %t %t/Main.private.swiftinterface -module-name Main
3636

37-
/// Serialize and deserialize this module, then print.
38-
// RUN: %target-swift-frontend -emit-module %s -emit-module-path %t/Merged-partial.swiftmodule -swift-version 5 -I %t -module-name Merged -enable-library-evolution
39-
// RUN: %target-swift-frontend -merge-modules %t/Merged-partial.swiftmodule -module-name Merged -emit-module -emit-module-path %t/Merged.swiftmodule -I %t -emit-module-interface-path %t/Merged.swiftinterface -emit-private-module-interface-path %t/Merged.private.swiftinterface -enable-library-evolution -swift-version 5 -I %t
40-
// RUN: %FileCheck -check-prefix=CHECK-PUBLIC %s < %t/Merged.swiftinterface
41-
// RUN: %FileCheck -check-prefix=CHECK-PRIVATE %s < %t/Merged.private.swiftinterface
42-
// RUN: %target-swift-frontend -typecheck-module-from-interface -I %t %t/Merged.swiftinterface
43-
// RUN: %target-swift-frontend -typecheck-module-from-interface -I %t %t/Merged.private.swiftinterface -module-name Merged
44-
4537
/// Both the public and private textual interfaces should have
4638
/// SPI information with `-library-level spi`.
4739
// RUN: %target-swift-frontend -typecheck %s -emit-module-interface-path %t/SPIModule.swiftinterface -emit-private-module-interface-path %t/SPIModule.private.swiftinterface -enable-library-evolution -swift-version 5 -I %t -module-name SPIModule -library-level spi
@@ -157,6 +149,34 @@ public struct PublicStruct {
157149
// CHECK-PUBLIC-NOT: spiWrappedDefault
158150
}
159151

152+
@_spi(S) public enum SPIEnum {
153+
// CHECK-PRIVATE: @_spi(S) public enum SPIEnum
154+
// CHECK-PUBLIC-NOT: SPIEnum
155+
156+
case spiEnumCase
157+
// CHECK-PRIVATE: case spiEnumCase
158+
// CHECK-PUBLIC-NOT: spiEnumCase
159+
}
160+
161+
public enum PublicEnum {
162+
case publicCase
163+
// CHECK-PUBLIC: case publicCase
164+
// CHECK-PRIVATE: case publicCase
165+
166+
@_spi(S) case spiCase
167+
// CHECK-PRIVATE: @_spi(S) case spiCase
168+
// CHECK-PUBLIC-NOT: spiCase
169+
170+
@_spi(S) case spiCaseA, spiCaseB
171+
// CHECK-PRIVATE: @_spi(S) case spiCaseA, spiCaseB
172+
// CHECK-PUBLIC-NOT: spiCaseA
173+
// CHECK-PUBLIC-NOT: spiCaseB
174+
175+
@_spi(S) case spiCaseWithPayload(_ c: SomeClass)
176+
// CHECK-PRIVATE: @_spi(S) case spiCaseWithPayload(_: {{.*}}.SomeClass)
177+
// CHECK-PUBLIC-NOT: spiCaseWithPayload
178+
}
179+
160180
@_spi(LocalSPI) public protocol SPIProto3 {
161181
// CHECK-PRIVATE: @_spi(LocalSPI) public protocol SPIProto3
162182
// CHECK-PUBLIC-NOT: SPIProto3

test/SPI/public_client.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ internalFunc() // expected-error {{cannot find 'internalFunc' in scope}}
2727
let c = SPIClass() // expected-error {{cannot find 'SPIClass' in scope}}
2828
let s = SPIStruct() // expected-error {{cannot find 'SPIStruct' in scope}}
2929
SPIEnum().spiMethod() // expected-error {{cannot find 'SPIEnum' in scope}}
30+
_ = PublicEnum.spiCase // expected-error {{'spiCase' is inaccessible due to '@_spi' protection level}}
3031

3132
var ps = PublicStruct()
3233
let _ = PublicStruct(alt_init: 1) // expected-error {{argument passed to call that takes no arguments}}

test/SPI/resilience.swift

Lines changed: 0 additions & 29 deletions
This file was deleted.

test/SPI/spi_client.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ print(s.spiVar)
4040

4141
SPIEnum().spiMethod()
4242
SPIEnum.A.spiMethod()
43+
_ = PublicEnum.spiCase
4344

4445
var ps = PublicStruct()
4546
let _ = PublicStruct(alt_init: 1)

test/SPI/spi_enum_element.swift

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// RUN: %target-typecheck-verify-swift -swift-version 5
2+
// RUN: %target-typecheck-verify-swift -swift-version 5 -enable-library-evolution
3+
4+
public enum PublicEnum {
5+
case publicCase
6+
@_spi(S) case spiCase
7+
}
8+
9+
@inlinable public func publicInlinableFunc(_ e: PublicEnum) {
10+
switch e {
11+
case .publicCase: break
12+
case .spiCase: break // FIXME: this should be diagnosed with "cannot use enum case 'spiCase' here; it is SPI"
13+
@unknown default: break
14+
}
15+
16+
if case .spiCase = e {} // FIXME: this should be diagnosed with "cannot use enum case 'spiCase' here; it is SPI"
17+
18+
_ = PublicEnum.spiCase // expected-error {{enum case 'spiCase' cannot be used in an '@inlinable' function because it is SPI}}
19+
}
20+
21+
@_spi(S)
22+
@inlinable public func spiInlinableFunc(_ e: PublicEnum) {
23+
switch e {
24+
case .publicCase: break
25+
case .spiCase: break
26+
@unknown default: break
27+
}
28+
29+
if case .spiCase = e {}
30+
31+
_ = PublicEnum.spiCase
32+
}
33+
34+
public struct PublicStruct {}
35+
@_spi(S) public struct SPIStruct {} // expected-note {{type declared here}}
36+
37+
public enum PublicEnumWithPayloads {
38+
case publicCasePublicPayload(_ s: PublicStruct)
39+
case publicCaseSPIPayload(_ s: SPIStruct) // expected-error {{cannot use struct 'SPIStruct' here; it is SPI}}
40+
@_spi(S) case spiCasePublicPayload(_ s: PublicStruct)
41+
@_spi(S) case spiCaseSPIPayload(_ s: SPIStruct)
42+
}
43+
44+
@_spi(S)
45+
public enum SPIEnumWithPayloads {
46+
case publicPayloadCase(_ s: PublicStruct)
47+
case spiPayloadCase(_ s: SPIStruct)
48+
}

test/SPI/spi_members.swift

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class Bar {
1414
public init() {}
1515
}
1616

17-
public struct Resilient {
17+
public struct ResilientStructSPIMembers {
1818
public init() {}
1919

2020
@_spi(Foo) public func method(_: Bar) {}
@@ -28,7 +28,7 @@ public struct Resilient {
2828
@_spi(Foo) @Wrapper public var wrappedProperty2 = Bar()
2929
}
3030

31-
@frozen public struct Good {
31+
@frozen public struct FrozenStructSPIMembers {
3232
public init() {}
3333

3434
@_spi(Foo) public func method(_: Bar) {}
@@ -55,7 +55,7 @@ public struct Resilient {
5555
// expected-error@-1 {{stored property 'wrappedProperty2' cannot be declared '@_spi' in a '@frozen' struct}}
5656
}
5757

58-
@frozen public struct Bad {
58+
@frozen public struct FrozenStructPublicMembers {
5959
public init() {}
6060

6161
public func method(_: Bar) {} // expected-error {{cannot use class 'Bar' here; it is SPI}}
@@ -83,7 +83,7 @@ public struct Resilient {
8383
// expected-error@-3 {{initializer 'init()' cannot be used in a property initializer in a '@frozen' type because it is SPI}}
8484
}
8585

86-
@frozen public struct BadPrivate {
86+
@frozen public struct FrozenStructPrivateMembers {
8787
private init() {}
8888

8989
private func method(_: Bar) {}
@@ -110,3 +110,24 @@ public struct Resilient {
110110
// expected-error@-2 {{class 'Bar' cannot be used in a property initializer in a '@frozen' type because it is SPI}}
111111
// expected-error@-3 {{initializer 'init()' cannot be used in a property initializer in a '@frozen' type because it is SPI}}
112112
}
113+
114+
public enum ResilientEnum {
115+
@_spi(S)
116+
case okSpiCase
117+
118+
@_spi(S)
119+
case okSpiCaseWithPayload(_: Int)
120+
}
121+
122+
@frozen
123+
public enum FrozenEnum {
124+
case okCase
125+
126+
@_spi(S) // expected-error {{enum case 'spiCase' cannot be declared '@_spi' in a '@frozen' enum}}
127+
case spiCase
128+
129+
case okCaseWithPayload(_: Int)
130+
131+
@_spi(S) // expected-error {{enum case 'spiCaseWithPayload' cannot be declared '@_spi' in a '@frozen' enum}}
132+
case spiCaseWithPayload(_: Int)
133+
}

0 commit comments

Comments
 (0)