Skip to content

Commit 4a8f81d

Browse files
authored
Special-case diagnostic for when you just need @unknown default (#21695)
This is a new feature of Swift 5 mode, so it deserves at least a little bit of explanation right in the diagnostic. If you have an otherwise-fully-covered switch but can't assume the enum is frozen, you'll now get this message: switch covers known cases, but 'MusicGenre' may have additional unknown values Furthermore, if the enum comes from a system header, it looks like this: switch covers known cases, but 'NSMusicGenre' may have additional unknown values, possibly added in future versions ...to further suggest the idea that even though your switch is covered /now/, it might not handle everything in the /future/. This extra bit is limited to system headers to avoid showing up on C enums defined in your own project, for which it sounds silly. (The main message is still valid though, since you can cram whatever you want into a C enum, and people use this pattern to implement "private cases".) rdar://problem/39367045
1 parent fc50e81 commit 4a8f81d

11 files changed

+87
-24
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4231,6 +4231,9 @@ NOTE(redundant_particular_literal_case_here,none,
42314231
"first occurrence of identical literal pattern is here", ())
42324232

42334233
WARNING(non_exhaustive_switch_warn,none, "switch must be exhaustive", ())
4234+
WARNING(non_exhaustive_switch_unknown_only,none,
4235+
"switch covers known cases, but %0 may have additional unknown values"
4236+
"%select{|, possibly added in future versions}1", (Type, bool))
42344237

42354238
WARNING(override_nsobject_hashvalue,none,
42364239
"override of 'NSObject.hashValue' is deprecated; "

lib/Sema/TypeCheckSwitchStmt.cpp

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,7 +1015,8 @@ namespace {
10151015
bool InEditor = TC.Context.LangOpts.DiagnosticsEditorMode;
10161016

10171017
// Decide whether we want an error or a warning.
1018-
auto mainDiagType = diag::non_exhaustive_switch;
1018+
Optional<decltype(diag::non_exhaustive_switch)> mainDiagType =
1019+
diag::non_exhaustive_switch;
10191020
if (unknownCase) {
10201021
switch (defaultReason) {
10211022
case RequiresDefault::EmptySwitchBody:
@@ -1042,16 +1043,25 @@ namespace {
10421043
switch (uncovered.checkDowngradeToWarning()) {
10431044
case DowngradeToWarning::No:
10441045
break;
1045-
case DowngradeToWarning::ForUnknownCase:
1046+
case DowngradeToWarning::ForUnknownCase: {
10461047
if (TC.Context.LangOpts.DebuggerSupport ||
10471048
TC.Context.LangOpts.Playground ||
10481049
!TC.getLangOpts().EnableNonFrozenEnumExhaustivityDiagnostics) {
10491050
// Don't require covering unknown cases in the debugger or in
10501051
// playgrounds.
10511052
return;
10521053
}
1053-
// Missing '@unknown' is just a warning.
1054-
mainDiagType = diag::non_exhaustive_switch_warn;
1054+
assert(defaultReason == RequiresDefault::No);
1055+
Type subjectType = Switch->getSubjectExpr()->getType();
1056+
bool shouldIncludeFutureVersionComment = false;
1057+
if (auto *theEnum = subjectType->getEnumOrBoundGenericEnum()) {
1058+
shouldIncludeFutureVersionComment =
1059+
theEnum->getParentModule()->isSystemModule();
1060+
}
1061+
TC.diagnose(startLoc, diag::non_exhaustive_switch_unknown_only,
1062+
subjectType, shouldIncludeFutureVersionComment);
1063+
mainDiagType = None;
1064+
}
10551065
break;
10561066
}
10571067

@@ -1066,7 +1076,7 @@ namespace {
10661076
return;
10671077
case RequiresDefault::UncoveredSwitch: {
10681078
OS << tok::kw_default << ":\n" << placeholder << "\n";
1069-
TC.diagnose(startLoc, mainDiagType);
1079+
TC.diagnose(startLoc, mainDiagType.getValue());
10701080
TC.diagnose(startLoc, diag::missing_several_cases, /*default*/true)
10711081
.fixItInsert(insertLoc, buffer.str());
10721082
}
@@ -1083,7 +1093,9 @@ namespace {
10831093
// If there's nothing else to diagnose, bail.
10841094
if (uncovered.isEmpty()) return;
10851095

1086-
TC.diagnose(startLoc, mainDiagType);
1096+
// Check if we still have to emit the main diganostic.
1097+
if (mainDiagType.hasValue())
1098+
TC.diagnose(startLoc, mainDiagType.getValue());
10871099

10881100
// Add notes to explain what's missing.
10891101
auto processUncoveredSpaces =

test/ClangImporter/enum-error.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,9 @@ func testError() {
8787
// CHECK: sil_witness_table shared [serialized] ExhaustiveError: _BridgedStoredNSError module __ObjC
8888
let terr = getErr()
8989
switch (terr) { case .TENone, .TEOne, .TETwo: break }
90-
// EXHAUSTIVE: [[@LINE-1]]:{{.+}}: warning: switch must be exhaustive
90+
// EXHAUSTIVE: [[@LINE-1]]:{{.+}}: warning: switch covers known cases, but 'TestError.Code' may have additional unknown values
9191
// EXHAUSTIVE: [[@LINE-2]]:{{.+}}: note: handle unknown values using "@unknown default"
9292

93-
// FIXME: This should still be an error because there are /known/ cases that
94-
// aren't covered.
9593
switch (terr) { case .TENone, .TEOne: break }
9694
// EXHAUSTIVE: [[@LINE-1]]:{{.+}}: error: switch must be exhaustive
9795
// EXHAUSTIVE: [[@LINE-2]]:{{.+}}: note: add missing case: '.TETwo'
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// RUN: %target-swift-frontend -typecheck %s -Xcc -isystem -Xcc %S/Inputs/custom-modules -verify -enable-nonfrozen-enum-exhaustivity-diagnostics
2+
3+
import EnumExhaustivity
4+
5+
func test(_ value: RegularEnum, _ exhaustiveValue: ExhaustiveEnum) {
6+
switch value { // expected-warning {{switch covers known cases, but 'RegularEnum' may have additional unknown values, possibly added in future versions}} expected-note {{handle unknown values using "@unknown default"}}
7+
case .A: break
8+
case .B: break
9+
}
10+
11+
switch exhaustiveValue { // always okay
12+
case .A: break
13+
case .B: break
14+
}
15+
}
16+
17+
func testAttributes(
18+
_ rete: RegularEnumTurnedExhaustive,
19+
_ arete: AnotherRegularEnumTurnedExhaustive,
20+
_ retetb: RegularEnumTurnedExhaustiveThenBackViaAPINotes,
21+
_ fdte: ForwardDeclaredTurnedExhaustive,
22+
_ fdo: ForwardDeclaredOnly
23+
) {
24+
switch rete {
25+
case .A, .B: break
26+
}
27+
28+
switch arete {
29+
case .A, .B: break
30+
}
31+
32+
switch retetb { // expected-warning {{switch covers known cases, but 'RegularEnumTurnedExhaustiveThenBackViaAPINotes' may have additional unknown values, possibly added in future versions}} expected-note {{handle unknown values using "@unknown default"}}
33+
case .A, .B: break
34+
}
35+
36+
switch fdte {
37+
case .A, .B: break
38+
}
39+
40+
switch fdo {
41+
case .A, .B: break
42+
}
43+
}
44+
45+
func testUnavailableCases(_ value: UnavailableCases) {
46+
switch value { // okay
47+
case .A: break
48+
case .B: break
49+
}
50+
}

test/ClangImporter/enum-exhaustivity.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import EnumExhaustivity
1818

1919
func test(_ value: RegularEnum, _ exhaustiveValue: ExhaustiveEnum) {
20-
switch value { // expected-error {{switch must be exhaustive}} expected-note {{handle unknown values using "@unknown default"}}
20+
switch value { // expected-error {{switch covers known cases, but 'RegularEnum' may have additional unknown values}} expected-note {{handle unknown values using "@unknown default"}}
2121
case .A: break
2222
case .B: break
2323
}
@@ -43,7 +43,7 @@ func testAttributes(
4343
case .A, .B: break
4444
}
4545

46-
switch retetb { // expected-error {{switch must be exhaustive}} expected-note {{handle unknown values using "@unknown default"}}
46+
switch retetb { // expected-error {{switch covers known cases, but 'RegularEnumTurnedExhaustiveThenBackViaAPINotes' may have additional unknown values}} expected-note {{handle unknown values using "@unknown default"}}
4747
case .A, .B: break
4848
}
4949

test/ClangImporter/enum-inferred-exhaustivity.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66

77
func test(_ value: EnumWithDefaultExhaustivity) {
88
// We want to assume such enums are non-frozen.
9-
switch value { // expected-error {{switch must be exhaustive}} expected-note {{handle unknown values using "@unknown default"}}
9+
switch value { // expected-error {{switch covers known cases, but 'EnumWithDefaultExhaustivity' may have additional unknown values}} expected-note {{handle unknown values using "@unknown default"}}
1010
case .loneCase: break
1111
}
1212
}
1313

1414
func test(_ value: EnumWithSpecialAttributes) {
1515
// Same, but with the attributes macro shipped in the Xcode 9 SDKs.
16-
switch value { // expected-error {{switch must be exhaustive}} expected-note {{handle unknown values using "@unknown default"}}
16+
switch value { // expected-error {{switch covers known cases, but 'EnumWithSpecialAttributes' may have additional unknown values}} expected-note {{handle unknown values using "@unknown default"}}
1717
case .loneCase: break
1818
}
1919
}

test/ClangImporter/enum-new.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ _ = .Red as Color
55
_ = .Cyan as MoreColor
66

77
func test() {
8-
switch getColor() { // expected-warning {{switch must be exhaustive}} expected-note{{handle unknown values using "@unknown default"}}
8+
switch getColor() { // expected-warning {{switch covers known cases, but 'Color' may have additional unknown values}} expected-note{{handle unknown values using "@unknown default"}}
99
case .Red, .Blue, .Green: break
1010
}
1111

12-
switch getMoreColor() { // expected-warning {{switch must be exhaustive}} expected-note{{handle unknown values using "@unknown default"}}
12+
switch getMoreColor() { // expected-warning {{switch covers known cases, but 'MoreColor' may have additional unknown values}} expected-note{{handle unknown values using "@unknown default"}}
1313
case .Yellow, .Magenta, .Black, .Cyan: break
1414
}
1515

test/ClangImporter/enum-objc.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// REQUIRES: objc_interop
44

55
func test(_ value: SwiftEnum, _ exhaustiveValue: ExhaustiveEnum) {
6-
switch value { // expected-warning {{switch must be exhaustive}} expected-note {{handle unknown values using "@unknown default"}}
6+
switch value { // expected-warning {{switch covers known cases, but 'SwiftEnum' may have additional unknown values}} expected-note {{handle unknown values using "@unknown default"}}
77
case .one: break
88
case .two: break
99
case .three: break

test/Sema/exhaustive_switch.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,7 @@ public func testNonExhaustive(_ value: NonExhaustive, _ payload: NonExhaustivePa
802802
case .a: break
803803
}
804804

805-
switch value { // expected-warning {{switch must be exhaustive}} {{none}} expected-note {{handle unknown values using "@unknown default"}} {{3-3=@unknown default:\n<#fatalError#>()\n}}
805+
switch value { // expected-warning {{switch covers known cases, but 'NonExhaustive' may have additional unknown values}} {{none}} expected-note {{handle unknown values using "@unknown default"}} {{3-3=@unknown default:\n<#fatalError#>()\n}}
806806
case .a: break
807807
case .b: break
808808
}
@@ -834,7 +834,7 @@ public func testNonExhaustive(_ value: NonExhaustive, _ payload: NonExhaustivePa
834834
}
835835

836836
// Test being part of other spaces.
837-
switch value as Optional { // expected-warning {{switch must be exhaustive}} {{none}} expected-note {{add missing case: '.some(_)'}}
837+
switch value as Optional { // expected-warning {{switch covers known cases, but 'Optional<NonExhaustive>' may have additional unknown values}} {{none}} expected-note {{add missing case: '.some(_)'}}
838838
case .a?: break
839839
case .b?: break
840840
case nil: break
@@ -852,7 +852,7 @@ public func testNonExhaustive(_ value: NonExhaustive, _ payload: NonExhaustivePa
852852
case nil: break
853853
} // no-warning
854854

855-
switch (value, flag) { // expected-warning {{switch must be exhaustive}} {{none}} expected-note {{add missing case: '(_, false)'}}
855+
switch (value, flag) { // expected-warning {{switch covers known cases, but '(NonExhaustive, Bool)' may have additional unknown values}} {{none}} expected-note {{add missing case: '(_, false)'}}
856856
case (.a, _): break
857857
case (.b, false): break
858858
case (_, true): break
@@ -865,7 +865,7 @@ public func testNonExhaustive(_ value: NonExhaustive, _ payload: NonExhaustivePa
865865
@unknown case _: break
866866
} // no-warning
867867

868-
switch (flag, value) { // expected-warning {{switch must be exhaustive}} {{none}} expected-note {{add missing case: '(false, _)'}}
868+
switch (flag, value) { // expected-warning {{switch covers known cases, but '(Bool, NonExhaustive)' may have additional unknown values}} {{none}} expected-note {{add missing case: '(false, _)'}}
869869
case (_, .a): break
870870
case (false, .b): break
871871
case (true, _): break
@@ -878,7 +878,7 @@ public func testNonExhaustive(_ value: NonExhaustive, _ payload: NonExhaustivePa
878878
@unknown case _: break
879879
} // no-warning
880880

881-
switch (value, value) { // expected-warning {{switch must be exhaustive}} {{none}} expected-note {{add missing case: '(_, _)'}}
881+
switch (value, value) { // expected-warning {{switch covers known cases, but '(NonExhaustive, NonExhaustive)' may have additional unknown values}} {{none}} expected-note {{add missing case: '(_, _)'}}
882882
case (.a, _), (_, .a): break
883883
case (.b, _), (_, .b): break
884884
}
@@ -894,7 +894,7 @@ public func testNonExhaustive(_ value: NonExhaustive, _ payload: NonExhaustivePa
894894
case .a: break
895895
}
896896

897-
switch payload { // expected-warning {{switch must be exhaustive}} {{none}} expected-note {{handle unknown values using "@unknown default"}} {{3-3=@unknown default:\n<#fatalError#>()\n}}
897+
switch payload { // expected-warning {{switch covers known cases, but 'NonExhaustivePayload' may have additional unknown values}} {{none}} expected-note {{handle unknown values using "@unknown default"}} {{3-3=@unknown default:\n<#fatalError#>()\n}}
898898
case .a: break
899899
case .b: break
900900
}

test/Sema/exhaustive_switch_testable.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func testFrozen(_ e: FrozenEnum) -> Int {
1717
}
1818

1919
func testNonFrozen(_ e: NonFrozenEnum) -> Int {
20-
// VERIFY-NON-FROZEN: exhaustive_switch_testable.swift:[[@LINE+1]]:{{[0-9]+}}: warning: switch must be exhaustive
20+
// VERIFY-NON-FROZEN: exhaustive_switch_testable.swift:[[@LINE+1]]:{{[0-9]+}}: warning: switch covers known cases, but 'NonFrozenEnum' may have additional unknown values
2121
switch e {
2222
case .a: return 1
2323
case .b, .c: return 2

test/stmt/nonexhaustive_switch_stmt_editor.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public func testNonExhaustive(_ value: NonExhaustive) {
1313
case .a: break
1414
}
1515

16-
switch value { // expected-warning {{switch must be exhaustive}}
16+
switch value { // expected-warning {{switch covers known cases, but 'NonExhaustive' may have additional unknown values}}
1717
// expected-note@-1 {{handle unknown values using "@unknown default"}} {{3-3=@unknown default:\n<#fatalError()#>\n}}
1818
case .a: break
1919
case .b: break

0 commit comments

Comments
 (0)