Skip to content

Commit b84bf05

Browse files
committed
[TypeChecker] Implement ExtensibleEnums feature exhaustivily handling
If an enum comes from a different module that has `ExtensibleEnums` feature enabled, unless it requires either `@unknown default:` or `@frozen` because it is allowed to introduce new cases in the future versions of the module.
1 parent 3cc24f7 commit b84bf05

File tree

3 files changed

+134
-4
lines changed

3 files changed

+134
-4
lines changed

lib/AST/Decl.cpp

+16-2
Original file line numberDiff line numberDiff line change
@@ -6696,8 +6696,22 @@ bool EnumDecl::hasOnlyCasesWithoutAssociatedValues() const {
66966696
}
66976697

66986698
bool EnumDecl::treatAsExhaustiveForDiags(const DeclContext *useDC) const {
6699-
return isFormallyExhaustive(useDC) ||
6700-
(useDC && getModuleContext()->inSamePackage(useDC->getParentModule()));
6699+
if (useDC) {
6700+
auto *enumModule = getModuleContext();
6701+
if (enumModule->inSamePackage(useDC->getParentModule()))
6702+
return true;
6703+
6704+
// If the module where enum is declared supports extensible enumerations
6705+
// and this enum is not explicitly marked as "@frozen", cross-module
6706+
// access cannot be exhaustive and requires `@unknown default:`.
6707+
if (enumModule->supportsExtensibleEnums() &&
6708+
!getAttrs().hasAttribute<FrozenAttr>()) {
6709+
if (useDC != enumModule->getDeclContext())
6710+
return false;
6711+
}
6712+
}
6713+
6714+
return isFormallyExhaustive(useDC);
67016715
}
67026716

67036717
bool EnumDecl::isFormallyExhaustive(const DeclContext *useDC) const {

lib/Sema/TypeCheckSwitchStmt.cpp

+8-2
Original file line numberDiff line numberDiff line change
@@ -1154,13 +1154,19 @@ namespace {
11541154
assert(defaultReason == RequiresDefault::No);
11551155
Type subjectType = Switch->getSubjectExpr()->getType();
11561156
bool shouldIncludeFutureVersionComment = false;
1157+
bool shouldDowngradeToWarning = true;
11571158
if (auto *theEnum = subjectType->getEnumOrBoundGenericEnum()) {
1159+
auto *enumModule = theEnum->getParentModule();
11581160
shouldIncludeFutureVersionComment =
1159-
theEnum->getParentModule()->isSystemModule();
1161+
enumModule->isSystemModule() ||
1162+
enumModule->supportsExtensibleEnums();
1163+
// Since the module enabled `ExtensibleEnums` feature they
1164+
// opted-in all of their clients into exhaustivity errors.
1165+
shouldDowngradeToWarning = !enumModule->supportsExtensibleEnums();
11601166
}
11611167
DE.diagnose(startLoc, diag::non_exhaustive_switch_unknown_only,
11621168
subjectType, shouldIncludeFutureVersionComment)
1163-
.warnUntilSwiftVersion(6);
1169+
.warnUntilSwiftVersionIf(shouldDowngradeToWarning, 6);
11641170
mainDiagType = std::nullopt;
11651171
}
11661172
break;
+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %empty-directory(%t/src)
3+
// RUN: split-file %s %t/src
4+
5+
/// Build the library
6+
// RUN: %target-swift-frontend -emit-module %t/src/Lib.swift \
7+
// RUN: -module-name Lib \
8+
// RUN: -emit-module-path %t/Lib.swiftmodule \
9+
// RUN: -enable-experimental-feature ExtensibleEnums
10+
11+
// Check that the errors are produced when using enums from module with `ExtensibleEnums` feature enabled.
12+
// RUN: %target-swift-frontend -typecheck %t/src/TestChecking.swift \
13+
// RUN: -swift-version 5 -module-name Client -I %t \
14+
// RUN: -verify
15+
16+
// Test to make sure that if the library and client are in the same package enums are checked exhaustively
17+
18+
/// Build the library
19+
// RUN: %target-swift-frontend -emit-module %t/src/Lib.swift \
20+
// RUN: -module-name Lib \
21+
// RUN: -package-name Test \
22+
// RUN: -emit-module-path %t/Lib.swiftmodule \
23+
// RUN: -enable-experimental-feature ExtensibleEnums
24+
25+
// Different module but the same package
26+
// RUN: %target-swift-frontend -typecheck %t/src/TestSamePackage.swift \
27+
// RUN: -swift-version 5 -module-name Client -I %t \
28+
// RUN: -package-name Test \
29+
// RUN: -verify
30+
31+
// REQUIRES: swift_feature_ExtensibleEnums
32+
33+
//--- Lib.swift
34+
35+
public enum E {
36+
case a
37+
}
38+
39+
@frozen
40+
public enum F {
41+
case a
42+
case b
43+
}
44+
45+
func test_same_module(e: E, f: F) {
46+
switch e { // Ok
47+
case .a: break
48+
}
49+
50+
switch f { // Ok
51+
case .a: break
52+
case .b: break
53+
}
54+
}
55+
56+
//--- TestChecking.swift
57+
import Lib
58+
59+
func test(e: E, f: F) {
60+
// `E` is not marked as `@frozen` which means it gets new semantics
61+
62+
switch e {
63+
// expected-error@-1 {{switch covers known cases, but 'E' may have additional unknown values, possibly added in future versions}}
64+
// expected-note@-2 {{handle unknown values using "@unknown default"}}
65+
case .a: break
66+
}
67+
68+
switch e { // Ok (no warnings)
69+
case .a: break
70+
@unknown default: break
71+
}
72+
73+
// `F` is marked as `@frozen` which means regular rules apply even with `ExtensibleEnums` feature enabled.
74+
75+
switch f { // Ok (no errors because `F` is `@frozen`)
76+
case .a: break
77+
case .b: break
78+
}
79+
80+
switch f { // expected-error {{switch must be exhaustive}} expected-note {{dd missing case: '.b'}}
81+
case .a: break
82+
}
83+
84+
switch f { // expected-warning {{switch must be exhaustive}} expected-note {{dd missing case: '.b'}}
85+
case .a: break
86+
@unknown default: break
87+
}
88+
}
89+
90+
//--- TestSamePackage.swift
91+
import Lib
92+
93+
func test_no_default(e: E, f: F) {
94+
switch e { // Ok
95+
case .a: break
96+
}
97+
98+
switch e { // expected-warning {{switch must be exhaustive}} expected-note {{dd missing case: '.a'}}
99+
@unknown default: break
100+
}
101+
102+
switch f { // expected-error {{switch must be exhaustive}} expected-note {{dd missing case: '.b'}}
103+
case .a: break
104+
}
105+
106+
switch f { // expected-warning {{switch must be exhaustive}} expected-note {{dd missing case: '.b'}}
107+
case .a: break
108+
@unknown default: break
109+
}
110+
}

0 commit comments

Comments
 (0)