Skip to content

Commit 4c04a0f

Browse files
authored
Merge pull request swiftlang#21230 from CodaFi/casein
Lift The "Large Space Heuristic" For Real This Time
2 parents fd0a82b + 3102ed0 commit 4c04a0f

File tree

4 files changed

+353
-306
lines changed

4 files changed

+353
-306
lines changed

lib/Sema/TypeCheckSwitchStmt.cpp

Lines changed: 3 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,6 @@ namespace {
115115
Identifier Head;
116116
std::forward_list<Space> Spaces;
117117

118-
// NB: This constant is arbitrary. Anecdotally, the Space Engine is
119-
// capable of efficiently handling Spaces of around size 200, but it would
120-
// potentially push an enormous fixit on the user.
121-
static const size_t MAX_SPACE_SIZE = 128;
122-
123118
size_t computeSize(TypeChecker &TC, const DeclContext *DC,
124119
SmallPtrSetImpl<TypeBase *> &cache) const {
125120
switch (getKind()) {
@@ -250,10 +245,6 @@ namespace {
250245
return computeSize(TC, DC, cache);
251246
}
252247

253-
static size_t getMaximumSize() {
254-
return MAX_SPACE_SIZE;
255-
}
256-
257248
bool isEmpty() const { return getKind() == SpaceKind::Empty; }
258249

259250
bool isAllowedButNotRequired() const {
@@ -895,103 +886,6 @@ namespace {
895886
}
896887
}
897888

898-
/// Estimate how big is the search space that exhaustivity
899-
/// checker needs to cover, based on the total space and information
900-
/// from the `switch` statement itself. Some of the easy situations
901-
/// like `case .foo(let bar)` don't really contribute to the complexity
902-
/// of the search so their sub-space sizes could be excluded from
903-
/// consideration.
904-
///
905-
/// \param total The total space to check.
906-
/// \param covered The space covered by the `case` statements in the switch.
907-
///
908-
/// \returns The size of the search space exhastivity checker has to check.
909-
size_t estimateSearchSpaceSize(const Space &total, const Space &covered) {
910-
switch (PairSwitch(total.getKind(), covered.getKind())) {
911-
PAIRCASE(SpaceKind::Type, SpaceKind::Type): {
912-
return total.getType()->isEqual(covered.getType())
913-
? 0
914-
: total.getSize(TC, DC);
915-
}
916-
PAIRCASE(SpaceKind::Type, SpaceKind::Disjunct):
917-
PAIRCASE(SpaceKind::Type, SpaceKind::Constructor): {
918-
if (!Space::canDecompose(total.getType(), DC))
919-
break;
920-
921-
auto decomposition = Space::decompose(TC, DC, total.getType());
922-
return estimateSearchSpaceSize(decomposition, covered);
923-
}
924-
925-
PAIRCASE(SpaceKind::Disjunct, SpaceKind::Disjunct):
926-
PAIRCASE(SpaceKind::Disjunct, SpaceKind::Constructor): {
927-
auto &spaces = total.getSpaces();
928-
return std::accumulate(spaces.begin(), spaces.end(), 0,
929-
[&](size_t totalSize, const Space &space) {
930-
return totalSize + estimateSearchSpaceSize(
931-
space, covered);
932-
});
933-
}
934-
935-
// Search space size computation is not commutative, because it
936-
// tries to check if space on right-hand side is covering any
937-
// portion of the "total" space on the left.
938-
PAIRCASE(SpaceKind::Constructor, SpaceKind::Disjunct): {
939-
for (const auto &space : covered.getSpaces()) {
940-
// enum E { case foo }
941-
// func bar(_ lhs: E, _ rhs: E) {
942-
// switch (lhs, rhs) {
943-
// case (_, _): break
944-
// }
945-
if (total == space)
946-
return 0;
947-
948-
if (!space.isSubspace(total, TC, DC))
949-
continue;
950-
951-
if (estimateSearchSpaceSize(total, space) == 0)
952-
return 0;
953-
}
954-
break;
955-
}
956-
957-
PAIRCASE(SpaceKind::Constructor, SpaceKind::Constructor): {
958-
if (total.getHead() != covered.getHead())
959-
break;
960-
961-
auto &lhs = total.getSpaces();
962-
auto &rhs = covered.getSpaces();
963-
964-
if (std::distance(lhs.begin(), lhs.end()) !=
965-
std::distance(rhs.begin(), rhs.end()))
966-
return total.getSize(TC, DC);
967-
968-
auto i = lhs.begin();
969-
auto j = rhs.begin();
970-
971-
size_t totalSize = 0;
972-
for (; i != lhs.end() && j != rhs.end(); ++i, ++j) {
973-
// The only light-weight checking we can do
974-
// is when sub-spaces on both sides are types
975-
// otherwise we'd have to decompose, which
976-
// is too heavy, so let's just return total
977-
// space size if such situation is detected.
978-
if (i->getKind() != SpaceKind::Type ||
979-
j->getKind() != SpaceKind::Type)
980-
return total.getSize(TC, DC);
981-
982-
totalSize += estimateSearchSpaceSize(*i, *j);
983-
}
984-
985-
return totalSize;
986-
}
987-
988-
default:
989-
break;
990-
}
991-
992-
return total.getSize(TC, DC);
993-
}
994-
995889
void checkExhaustiveness(bool limitedChecking) {
996890
// If the type of the scrutinee is uninhabited, we're already dead.
997891
// Allow any well-typed patterns through.
@@ -1008,7 +902,6 @@ namespace {
1008902
return;
1009903
}
1010904

1011-
bool sawRedundantPattern = false;
1012905
const CaseStmt *unknownCase = nullptr;
1013906
SmallVector<Space, 4> spaces;
1014907
for (auto *caseBlock : Switch->getCases()) {
@@ -1032,8 +925,6 @@ namespace {
1032925

1033926
if (!projection.isEmpty() &&
1034927
projection.isSubspace(Space::forDisjunct(spaces), TC, DC)) {
1035-
sawRedundantPattern |= true;
1036-
1037928
TC.diagnose(caseItem.getStartLoc(),
1038929
diag::redundant_particular_case)
1039930
.highlight(caseItem.getSourceRange());
@@ -1058,18 +949,11 @@ namespace {
1058949
Space totalSpace = Space::forType(subjectType, Identifier());
1059950
Space coveredSpace = Space::forDisjunct(spaces);
1060951

1061-
const size_t searchSpaceSizeEstimate =
1062-
estimateSearchSpaceSize(totalSpace, coveredSpace);
1063-
if (searchSpaceSizeEstimate > Space::getMaximumSize()) {
1064-
diagnoseCannotCheck(sawRedundantPattern, totalSpace, coveredSpace,
1065-
unknownCase);
1066-
return;
1067-
}
1068952
unsigned minusCount = 0;
1069953
auto diff = totalSpace.minus(coveredSpace, TC, DC, &minusCount);
1070954
if (!diff) {
1071-
diagnoseCannotCheck(sawRedundantPattern, totalSpace, coveredSpace,
1072-
unknownCase);
955+
diagnoseMissingCases(RequiresDefault::SpaceTooLarge, Space(),
956+
unknownCase);
1073957
return;
1074958
}
1075959

@@ -1115,26 +999,7 @@ namespace {
1115999
UncoveredSwitch,
11161000
SpaceTooLarge,
11171001
};
1118-
1119-
void diagnoseCannotCheck(const bool sawRedundantPattern,
1120-
const Space &totalSpace,
1121-
const Space &coveredSpace,
1122-
const CaseStmt *unknownCase) {
1123-
// Because the space is large or the check is too slow,
1124-
// we have to extend the size
1125-
// heuristic to compensate for actually exhaustively pattern matching
1126-
// over enormous spaces. In this case, if the covered space covers
1127-
// as much as the total space, and there were no duplicates, then we
1128-
// can assume the user did the right thing and that they don't need
1129-
// a 'default' to be inserted.
1130-
// FIXME: Do something sensible for non-frozen enums.
1131-
if (!sawRedundantPattern &&
1132-
coveredSpace.getSize(TC, DC) >= totalSpace.getSize(TC, DC))
1133-
return;
1134-
diagnoseMissingCases(RequiresDefault::SpaceTooLarge, Space(),
1135-
unknownCase);
1136-
}
1137-
1002+
11381003
void diagnoseMissingCases(RequiresDefault defaultReason, Space uncovered,
11391004
const CaseStmt *unknownCase = nullptr) {
11401005
SourceLoc startLoc = Switch->getStartLoc();

test/Compatibility/exhaustive_switch.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -441,8 +441,8 @@ enum ContainsOverlyLargeEnum {
441441
}
442442

443443
func quiteBigEnough() -> Bool {
444-
switch (OverlyLargeSpaceEnum.case1, OverlyLargeSpaceEnum.case2) { // expected-error {{the compiler is unable to check that this switch is exhaustive in reasonable time}}
445-
// expected-note@-1 {{do you want to add a default clause?}}
444+
switch (OverlyLargeSpaceEnum.case1, OverlyLargeSpaceEnum.case2) { // expected-error {{switch must be exhaustive}}
445+
// expected-note@-1 132 {{add missing case:}}
446446
case (.case0, .case0): return true
447447
case (.case1, .case1): return true
448448
case (.case2, .case2): return true
@@ -457,8 +457,8 @@ func quiteBigEnough() -> Bool {
457457
case (.case11, .case11): return true
458458
}
459459

460-
switch (OverlyLargeSpaceEnum.case1, OverlyLargeSpaceEnum.case2) { // expected-error {{the compiler is unable to check that this switch is exhaustive in reasonable time}}
461-
// expected-note@-1 {{do you want to add a default clause?}}
460+
switch (OverlyLargeSpaceEnum.case1, OverlyLargeSpaceEnum.case2) { // expected-error {{switch must be exhaustive}}
461+
// expected-note@-1 {{add missing case: '(.case11, _)'}}
462462
case (.case0, _): return true
463463
case (.case1, _): return true
464464
case (.case2, _): return true
@@ -472,7 +472,8 @@ func quiteBigEnough() -> Bool {
472472
case (.case10, _): return true
473473
}
474474

475-
switch (OverlyLargeSpaceEnum.case1, OverlyLargeSpaceEnum.case2) { // expected-error {{the compiler is unable to check that this switch is exhaustive in reasonable time}}
475+
switch (OverlyLargeSpaceEnum.case1, OverlyLargeSpaceEnum.case2) { // expected-warning {{switch must be exhaustive}}
476+
// expected-note@-1 {{add missing case: '(.case11, _)'}}
476477
case (.case0, _): return true
477478
case (.case1, _): return true
478479
case (.case2, _): return true
@@ -484,7 +485,7 @@ func quiteBigEnough() -> Bool {
484485
case (.case8, _): return true
485486
case (.case9, _): return true
486487
case (.case10, _): return true
487-
@unknown default: return false // expected-note {{remove '@unknown' to handle remaining values}} {{3-12=}}
488+
@unknown default: return false
488489
}
489490

490491

0 commit comments

Comments
 (0)