Skip to content

Commit 1fd4444

Browse files
author
Adam Cmiel
committed
[StrictExhaustiveSwitches] flow through default case to space checking
1 parent 1cf8d2e commit 1fd4444

File tree

4 files changed

+209
-33
lines changed

4 files changed

+209
-33
lines changed

include/swift/AST/DiagnosticGroups.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ GROUP(TemporaryPointers, "temporary-pointers")
8686
GROUP(TrailingClosureMatching, "trailing-closure-matching")
8787
GROUP(UnknownWarningGroup, "unknown-warning-group")
8888
GROUP(WeakMutability, "weak-mutability")
89+
GROUP(StrictExhaustiveSwitches, "strict-exhaustive-switches")
8990

9091
GROUP_LINK(PerformanceHints, ExistentialType)
9192
GROUP_LINK(PerformanceHints, ReturnTypeImplicitCopy)

include/swift/AST/DiagnosticsSema.def

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ ERROR(could_not_find_imported_enum_case,none,
114114

115115
NOTE(did_you_mean_raw_type,none,
116116
"did you mean to specify a raw type on the enum declaration?", ())
117-
117+
118118
NOTE(did_you_mean_generic_param_as_conformance,none,
119119
"did you mean to declare %0 as a protocol conformance for %1?", (DeclName, Type))
120120

@@ -1302,7 +1302,7 @@ REMARK(transitive_dependency_behavior,none,
13021302
"%1 has %select{a required|an optional|an ignored}2 "
13031303
"transitive dependency on '%0'",
13041304
(StringRef, Identifier, unsigned))
1305-
1305+
13061306
REMARK(explicit_interface_build_skipped,none,
13071307
"Skipped rebuilding module at %0 - up-to-date",
13081308
(StringRef))
@@ -1556,7 +1556,7 @@ ERROR(optional_self_not_unwrapped,none,
15561556
"to make control flow explicit", ())
15571557
NOTE(optional_self_chain,none,
15581558
"reference 'self?.' explicitly", ())
1559-
1559+
15601560
ERROR(missing_unwrap_optional_try,none,
15611561
"value of optional type %0 not unwrapped; did you mean to use 'try!' "
15621562
"or chain with '?'?",
@@ -1567,7 +1567,7 @@ NOTE(missing_forced_downcast, none,
15671567
"did you mean to use 'as!' to force downcast?", ())
15681568
NOTE(missing_optional_downcast, none,
15691569
"did you mean to use 'as?' to conditionally downcast?", ())
1570-
1570+
15711571
WARNING(coercion_may_fail_warning,none,
15721572
"coercion from %0 to %1 may fail; use 'as?' or 'as!' instead",
15731573
(Type, Type))
@@ -4073,7 +4073,7 @@ NOTE(automatic_protocol_synthesis_unsupported,none,
40734073
"automatic synthesis of %0 is not supported for %kindonly1 declarations",
40744074
(const ProtocolDecl *, const NominalTypeDecl *))
40754075
NOTE(comparable_synthesis_raw_value_not_allowed, none,
4076-
"enum declares raw type %0, preventing synthesized conformance of %1 to %2",
4076+
"enum declares raw type %0, preventing synthesized conformance of %1 to %2",
40774077
(Type, Type, Type))
40784078

40794079
// Dynamic Self
@@ -4679,15 +4679,15 @@ ERROR(unordered_adjacent_operators,none,
46794679
ERROR(missing_builtin_precedence_group,none,
46804680
"broken standard library: missing builtin precedence group %0",
46814681
(Identifier))
4682-
WARNING(nan_comparison, none,
4682+
WARNING(nan_comparison, none,
46834683
"comparison with '.nan' using %0 is always %select{false|true}1, use "
46844684
"'%2.isNaN' to check if '%3' %select{is not a number|is a number}1",
46854685
(Identifier, bool, StringRef, StringRef))
4686-
WARNING(nan_comparison_without_isnan, none,
4687-
"comparison with '.nan' using %0 is always %select{false|true}1",
4686+
WARNING(nan_comparison_without_isnan, none,
4687+
"comparison with '.nan' using %0 is always %select{false|true}1",
46884688
(Identifier, bool))
4689-
WARNING(nan_comparison_both_nan, none,
4690-
"'.nan' %0 '.nan' is always %select{false|true}1",
4689+
WARNING(nan_comparison_both_nan, none,
4690+
"'.nan' %0 '.nan' is always %select{false|true}1",
46914691
(StringRef, bool))
46924692

46934693
// If you change this, also change enum TryKindForDiagnostics.
@@ -4826,7 +4826,7 @@ ERROR(should_use_empty_dictionary_literal,none,
48264826
WARNING(duplicated_literal_keys_in_dictionary_literal, none,
48274827
"dictionary literal of type %0 has duplicate entries for %1 literal key%select{ %3|}2",
48284828
(Type, StringRef, bool, StringRef))
4829-
NOTE(duplicated_key_declared_here, none,
4829+
NOTE(duplicated_key_declared_here, none,
48304830
"duplicate key declared here", ())
48314831

48324832
// Generic specializations
@@ -5732,7 +5732,7 @@ NOTE(property_requires_actor,none,
57325732
"initializer for %kind0 is %1",
57335733
(const VarDecl *, ActorIsolation))
57345734
ERROR(isolated_default_argument_context,none,
5735-
"%0 default value in a %1 context",
5735+
"%0 default value in a %1 context",
57365736
(ActorIsolation, ActorIsolation))
57375737
ERROR(conflicting_default_argument_isolation,none,
57385738
"default argument cannot be both %0 and %1",
@@ -6489,7 +6489,7 @@ ERROR(differentiable_function_type_void_result,
64896489
"differentiable inout parameter, i.e. a non-'@noDerivative' parameter "
64906490
"whose type conforms to 'Differentiable'",
64916491
())
6492-
ERROR(differentiable_function_type_no_differentiability_parameters,
6492+
ERROR(differentiable_function_type_no_differentiability_parameters,
64936493
none,
64946494
"'@differentiable' function type requires at least one differentiability "
64956495
"parameter, i.e. a non-'@noDerivative' parameter whose type conforms to "
@@ -7531,6 +7531,9 @@ NOTE(missing_several_cases,none,
75317531
NOTE(missing_unknown_case,none,
75327532
"handle unknown values using \"@unknown default\"", ())
75337533

7534+
GROUPED_WARNING(decompose_default_case, StrictExhaustiveSwitches,DefaultIgnore,
7535+
"switch can be made exhaustive without use of 'default'; replace with known cases", ())
7536+
75347537
NOTE(non_exhaustive_switch_drop_unknown,none,
75357538
"remove '@unknown' to handle remaining values", ())
75367539

@@ -8569,7 +8572,7 @@ ERROR(safe_and_unsafe_attr,none,
85698572
"%kindbase0 cannot be both '@safe' and '@unsafe'", (const Decl *))
85708573

85718574
GROUPED_WARNING(unsafe_superclass,StrictMemorySafety,none,
8572-
"%kindbase0 has superclass involving unsafe type %1",
8575+
"%kindbase0 has superclass involving unsafe type %1",
85738576
(const ValueDecl *, Type))
85748577

85758578
GROUPED_WARNING(conformance_involves_unsafe,StrictMemorySafety,none,

lib/Sema/TypeCheckSwitchStmt.cpp

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,10 @@ namespace {
973973
}
974974
}
975975

976+
static bool hasStrictExhaustiveChecksEnabled(ASTContext &Context) {
977+
return !(Context.Diags.isIgnoredDiagnostic(diag::decompose_default_case.ID));
978+
}
979+
976980
void checkExhaustiveness(bool limitedChecking) {
977981
// If the type of the scrutinee is uninhabited, we're already dead.
978982
// Allow any well-typed patterns through.
@@ -985,11 +989,13 @@ namespace {
985989
if (limitedChecking) {
986990
// Reject switch statements with empty blocks.
987991
if (Switch->getCases().empty())
988-
diagnoseMissingCases(RequiresDefault::EmptySwitchBody, Space());
992+
diagnoseMissingCases(RequiresDefault::EmptySwitchBody, Space(),
993+
nullptr/* unknown default */, nullptr/* default */);
989994
return;
990995
}
991996

992997
const CaseStmt *unknownCase = nullptr;
998+
const CaseStmt *defaultCase = nullptr;
993999
SmallVector<Space, 4> spaces;
9941000
auto &DE = Context.Diags;
9951001
for (auto *caseBlock : Switch->getCases()) {
@@ -1006,8 +1012,14 @@ namespace {
10061012
continue;
10071013

10081014
// Space is trivially covered with a default clause.
1009-
if (caseItem.isDefault())
1010-
return;
1015+
if (caseItem.isDefault()) {
1016+
if (!hasStrictExhaustiveChecksEnabled(Context)) {
1017+
return;
1018+
}
1019+
assert(defaultCase == nullptr && "multiple default cases");
1020+
defaultCase = caseBlock;
1021+
continue;
1022+
}
10111023

10121024
Space projection = projectPattern(caseItem.getPattern());
10131025
bool isRedundant = !projection.isEmpty() &&
@@ -1046,7 +1058,7 @@ namespace {
10461058
auto diff = totalSpace.minus(coveredSpace, DC, &minusCount);
10471059
if (!diff) {
10481060
diagnoseMissingCases(RequiresDefault::SpaceTooLarge, Space(),
1049-
unknownCase);
1061+
unknownCase, defaultCase);
10501062
return;
10511063
}
10521064

@@ -1075,19 +1087,19 @@ namespace {
10751087
SmallVector<Space, 4> spaces;
10761088
Space::decomposeDisjuncts(DC, uncovered.getType(), {}, spaces);
10771089
diagnoseMissingCases(RequiresDefault::No, Space::forDisjunct(spaces),
1078-
unknownCase);
1079-
} else {
1090+
unknownCase, defaultCase);
1091+
} else if (!defaultCase) {
10801092
diagnoseMissingCases(Switch->getCases().empty()
10811093
? RequiresDefault::EmptySwitchBody
10821094
: RequiresDefault::UncoveredSwitch,
1083-
uncovered, unknownCase);
1095+
uncovered, unknownCase, defaultCase);
10841096
}
10851097
return;
10861098
}
10871099

1088-
diagnoseMissingCases(RequiresDefault::No, uncovered, unknownCase);
1100+
diagnoseMissingCases(RequiresDefault::No, uncovered, unknownCase, defaultCase);
10891101
}
1090-
1102+
10911103
enum class RequiresDefault {
10921104
No,
10931105
EmptySwitchBody,
@@ -1096,14 +1108,41 @@ namespace {
10961108
};
10971109

10981110
void diagnoseMissingCases(RequiresDefault defaultReason, Space uncovered,
1099-
const CaseStmt *unknownCase = nullptr) {
1111+
const CaseStmt *unknownCase, const CaseStmt *defaultCase) {
1112+
assert(!(unknownCase && defaultCase) && "both @unknown default and default should be caught earlier");
11001113
if (!Switch->getLBraceLoc().isValid()) {
11011114
// There is no '{' in the switch statement, which we already diagnosed
11021115
// in the parser. So there's no real body to speak of and it doesn't
11031116
// make sense to emit diagnostics about missing cases.
11041117
return;
11051118
}
1119+
1120+
// Decide whether we want an error or a warning.
1121+
std::optional<decltype(diag::non_exhaustive_switch)> mainDiagType =
1122+
diag::non_exhaustive_switch;
1123+
1124+
bool downgrade = false;
1125+
11061126
auto &DE = Context.Diags;
1127+
if (defaultCase) {
1128+
if (defaultReason != RequiresDefault::No) {
1129+
// We actually do need the default,
1130+
// so it does handle the space, do not emit diagnostics
1131+
return;
1132+
}
1133+
1134+
// Switch actually is exhaustive with known cases,
1135+
// so don't emit exhaustive diagnostic
1136+
mainDiagType = std::nullopt;
1137+
1138+
// Warn that the default case is not needed, remove the
1139+
// default case and enumerate the cases below
1140+
DE.diagnose(defaultCase->getStartLoc(),
1141+
diag::decompose_default_case)
1142+
.fixItRemoveChars(defaultCase->getStartLoc(),
1143+
defaultCase->getEndLoc());
1144+
}
1145+
11071146
SourceLoc startLoc = Switch->getStartLoc();
11081147
SourceLoc insertLoc;
11091148
if (unknownCase)
@@ -1114,10 +1153,6 @@ namespace {
11141153
llvm::SmallString<128> buffer;
11151154
llvm::raw_svector_ostream OS(buffer);
11161155

1117-
// Decide whether we want an error or a warning.
1118-
std::optional<decltype(diag::non_exhaustive_switch)> mainDiagType =
1119-
diag::non_exhaustive_switch;
1120-
bool downgrade = false;
11211156
if (unknownCase) {
11221157
switch (defaultReason) {
11231158
case RequiresDefault::EmptySwitchBody:
@@ -1224,8 +1259,7 @@ namespace {
12241259

12251260
// Add notes to explain what's missing.
12261261
auto processUncoveredSpaces =
1227-
[&](llvm::function_ref<void(const Space &space,
1228-
bool onlyOneUncoveredSpace)> process) {
1262+
[&](llvm::function_ref<void(const Space &space)> process) {
12291263

12301264
// Flatten away all disjunctions.
12311265
SmallVector<Space, 4> flats;
@@ -1279,7 +1313,7 @@ namespace {
12791313
continue;
12801314
}
12811315

1282-
process(flat, flats.size() == 1);
1316+
process(flat);
12831317
}
12841318
};
12851319

@@ -1300,8 +1334,7 @@ namespace {
13001334
SmallString<128> missingSeveralCasesFixIt;
13011335
int diagnosedCases = 0;
13021336

1303-
processUncoveredSpaces([&](const Space &space,
1304-
bool onlyOneUncoveredSpace) {
1337+
processUncoveredSpaces([&](const Space &space) {
13051338
llvm::SmallString<64> fixItBuffer;
13061339
llvm::raw_svector_ostream fixItOS(fixItBuffer);
13071340
if (space.getKind() == SpaceKind::UnknownCase) {

0 commit comments

Comments
 (0)