Skip to content

Commit 9b78d53

Browse files
authored
Merge pull request #17898 from xedin/rdar-40400251-4.2
[4.2][SpaceEngine] Improve total search space size estimation
2 parents b27a34b + 04e2052 commit 9b78d53

File tree

2 files changed

+143
-7
lines changed

2 files changed

+143
-7
lines changed

lib/Sema/TypeCheckSwitchStmt.cpp

Lines changed: 106 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,7 +1241,105 @@ namespace {
12411241
return false;
12421242
}
12431243
}
1244-
1244+
1245+
/// Estimate how big is the search space that exhaustivity
1246+
/// checker needs to cover, based on the total space and information
1247+
/// from the `switch` statement itself. Some of the easy situations
1248+
/// like `case .foo(let bar)` don't really contribute to the complexity
1249+
/// of the search so their sub-space sizes could be excluded from
1250+
/// consideration.
1251+
///
1252+
/// \param total The total space to check.
1253+
/// \param covered The space covered by the `case` statements in the switch.
1254+
///
1255+
/// \returns The size of the search space exhastivity checker has to check.
1256+
size_t estimateSearchSpaceSize(const Space &total, const Space &covered) {
1257+
switch (PairSwitch(total.getKind(), covered.getKind())) {
1258+
PAIRCASE(SpaceKind::Type, SpaceKind::Type): {
1259+
return total.getType()->isEqual(covered.getType())
1260+
? 0
1261+
: total.getSize(TC, DC);
1262+
}
1263+
PAIRCASE(SpaceKind::Type, SpaceKind::Disjunct):
1264+
PAIRCASE(SpaceKind::Type, SpaceKind::Constructor): {
1265+
if (!Space::canDecompose(total.getType(), DC))
1266+
break;
1267+
1268+
SmallVector<Space, 4> spaces;
1269+
Space::decompose(TC, DC, total.getType(), spaces);
1270+
return estimateSearchSpaceSize(Space::forDisjunct(spaces), covered);
1271+
}
1272+
1273+
PAIRCASE(SpaceKind::Disjunct, SpaceKind::Disjunct):
1274+
PAIRCASE(SpaceKind::Disjunct, SpaceKind::Constructor): {
1275+
auto &spaces = total.getSpaces();
1276+
return std::accumulate(spaces.begin(), spaces.end(), 0,
1277+
[&](size_t totalSize, const Space &space) {
1278+
return totalSize + estimateSearchSpaceSize(
1279+
space, covered);
1280+
});
1281+
}
1282+
1283+
// Search space size computation is not commutative, because it
1284+
// tries to check if space on right-hand side is covering any
1285+
// portion of the "total" space on the left.
1286+
PAIRCASE(SpaceKind::Constructor, SpaceKind::Disjunct): {
1287+
for (const auto &space : covered.getSpaces()) {
1288+
// enum E { case foo }
1289+
// func bar(_ lhs: E, _ rhs: E) {
1290+
// switch (lhs, rhs) {
1291+
// case (_, _): break
1292+
// }
1293+
if (total == space)
1294+
return 0;
1295+
1296+
if (!space.isSubspace(total, TC, DC))
1297+
continue;
1298+
1299+
if (estimateSearchSpaceSize(total, space) == 0)
1300+
return 0;
1301+
}
1302+
break;
1303+
}
1304+
1305+
PAIRCASE(SpaceKind::Constructor, SpaceKind::Constructor): {
1306+
if (total.getHead() != covered.getHead())
1307+
break;
1308+
1309+
auto &lhs = total.getSpaces();
1310+
auto &rhs = covered.getSpaces();
1311+
1312+
if (std::distance(lhs.begin(), lhs.end()) !=
1313+
std::distance(rhs.begin(), rhs.end()))
1314+
return total.getSize(TC, DC);
1315+
1316+
auto i = lhs.begin();
1317+
auto j = rhs.begin();
1318+
1319+
size_t totalSize = 0;
1320+
for (; i != lhs.end() && j != rhs.end(); ++i, ++j) {
1321+
// The only light-weight checking we can do
1322+
// is when sub-spaces on both sides are types
1323+
// otherwise we'd have to decompose, which
1324+
// is too heavy, so let's just return total
1325+
// space size if such situation is detected.
1326+
if (i->getKind() != SpaceKind::Type ||
1327+
j->getKind() != SpaceKind::Type)
1328+
return total.getSize(TC, DC);
1329+
1330+
totalSize += estimateSearchSpaceSize(*i, *j);
1331+
}
1332+
1333+
return totalSize;
1334+
}
1335+
1336+
default:
1337+
break;
1338+
}
1339+
1340+
return total.getSize(TC, DC);
1341+
}
1342+
12451343
void checkExhaustiveness(bool limitedChecking) {
12461344
// If the type of the scrutinee is uninhabited, we're already dead.
12471345
// Allow any well-typed patterns through.
@@ -1308,16 +1406,17 @@ namespace {
13081406
Space totalSpace = Space::forType(subjectType, Identifier());
13091407
Space coveredSpace = Space::forDisjunct(spaces);
13101408

1311-
const size_t totalSpaceSize = totalSpace.getSize(TC, DC);
1312-
if (totalSpaceSize > Space::getMaximumSize()) {
1313-
diagnoseCannotCheck(sawRedundantPattern, totalSpaceSize, coveredSpace,
1409+
const size_t searchSpaceSizeEstimate =
1410+
estimateSearchSpaceSize(totalSpace, coveredSpace);
1411+
if (searchSpaceSizeEstimate > Space::getMaximumSize()) {
1412+
diagnoseCannotCheck(sawRedundantPattern, totalSpace, coveredSpace,
13141413
unknownCase);
13151414
return;
13161415
}
13171416
unsigned minusCount = 0;
13181417
auto diff = totalSpace.minus(coveredSpace, TC, DC, &minusCount);
13191418
if (!diff) {
1320-
diagnoseCannotCheck(sawRedundantPattern, totalSpaceSize, coveredSpace,
1419+
diagnoseCannotCheck(sawRedundantPattern, totalSpace, coveredSpace,
13211420
unknownCase);
13221421
return;
13231422
}
@@ -1369,7 +1468,7 @@ namespace {
13691468
};
13701469

13711470
void diagnoseCannotCheck(const bool sawRedundantPattern,
1372-
const size_t totalSpaceSize,
1471+
const Space &totalSpace,
13731472
const Space &coveredSpace,
13741473
const CaseStmt *unknownCase) {
13751474
// Because the space is large or the check is too slow,
@@ -1381,7 +1480,7 @@ namespace {
13811480
// a 'default' to be inserted.
13821481
// FIXME: Do something sensible for non-frozen enums.
13831482
if (!sawRedundantPattern &&
1384-
coveredSpace.getSize(TC, DC) >= totalSpaceSize)
1483+
coveredSpace.getSize(TC, DC) >= totalSpace.getSize(TC, DC))
13851484
return;
13861485
diagnoseMissingCases(RequiresDefault::SpaceTooLarge, Space(),
13871486
unknownCase);

test/stmt/rdar40400251.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
enum E1 {
4+
case v1
5+
case v2
6+
case v3
7+
case v4
8+
case v5
9+
case v6
10+
indirect case v7(E1)
11+
}
12+
13+
enum E2 {
14+
case foo((E1, E1, E1)) // total size of this case is 7 ^ 3 + 1
15+
case bar(E1)
16+
case baz
17+
}
18+
19+
func foo(_ e: E2) {
20+
switch e {
21+
// expected-error@-1 {{switch must be exhaustive}}
22+
// expected-note@-2 {{add missing case: '.bar(_)'}}
23+
24+
case .foo(let tuple): break // expected-warning {{immutable value 'tuple' was never used; consider replacing with '_' or removing it}}
25+
case .baz: break
26+
}
27+
}
28+
29+
func bar(_ e: E2) {
30+
switch e {
31+
// expected-error@-1 {{switch must be exhaustive}}
32+
// expected-note@-2 {{add missing case: '.bar(_)'}}
33+
// expected-note@-3 {{add missing case: '.baz'}}
34+
35+
case .foo((_, _, _)): break
36+
}
37+
}

0 commit comments

Comments
 (0)