Skip to content

Cut down on exponential growth in checking switch exhaustivity. #21999

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 47 additions & 19 deletions lib/Sema/TypeCheckSwitchStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -506,12 +506,35 @@ namespace {
PAIRCASE (SpaceKind::Disjunct, SpaceKind::UnknownCase): {
SmallVector<Space, 4> smallSpaces;
for (auto s : this->getSpaces()) {
if (auto diff = s.minus(other, TC, DC, minusCount))
smallSpaces.push_back(*diff);
else
auto diff = s.minus(other, TC, DC, minusCount);
if (!diff)
return None;
if (diff->getKind() == SpaceKind::Disjunct) {
smallSpaces.append(diff->getSpaces().begin(),
diff->getSpaces().end());
} else {
smallSpaces.push_back(*diff);
}
}
return Space::forDisjunct(smallSpaces);

// Remove any of the later spaces that are contained entirely in an
// earlier one. Since we're not sorting by size, this isn't
// guaranteed to give us a minimal set, but it'll still reduce the
// general (A, B, C) - ((.a1, .b1, .c1) | (.a1, .b1, .c2)) problem.
// This is a quadratic operation but it saves us a LOT of work
// overall.
SmallVector<Space, 4> usefulSmallSpaces;
for (const Space &space : smallSpaces) {
bool alreadyHandled = llvm::any_of(usefulSmallSpaces,
[&](const Space &previousSpace) {
return space.isSubspace(previousSpace, TC, DC);
});
if (alreadyHandled)
continue;
usefulSmallSpaces.push_back(space);
}

return Space::forDisjunct(usefulSmallSpaces);
}
PAIRCASE (SpaceKind::Constructor, SpaceKind::Type):
return Space();
Expand Down Expand Up @@ -923,26 +946,31 @@ namespace {

Space projection = projectPattern(TC, caseItem.getPattern());

if (!projection.isEmpty() &&
projection.isSubspace(Space::forDisjunct(spaces), TC, DC)) {
bool isRedundant = !projection.isEmpty() &&
llvm::any_of(spaces, [&](const Space &handled) {
return projection.isSubspace(handled, TC, DC);
});
if (isRedundant) {
TC.diagnose(caseItem.getStartLoc(),
diag::redundant_particular_case)
.highlight(caseItem.getSourceRange());
continue;
} else {
Expr *cachedExpr = nullptr;
if (checkRedundantLiteral(caseItem.getPattern(), cachedExpr)) {
assert(cachedExpr && "Cache found hit but no expr?");
TC.diagnose(caseItem.getStartLoc(),
diag::redundant_particular_literal_case)
.highlight(caseItem.getSourceRange());
TC.diagnose(cachedExpr->getLoc(),
diag::redundant_particular_literal_case_here)
.highlight(cachedExpr->getSourceRange());
continue;
}
}
spaces.push_back(projection);

Expr *cachedExpr = nullptr;
if (checkRedundantLiteral(caseItem.getPattern(), cachedExpr)) {
assert(cachedExpr && "Cache found hit but no expr?");
TC.diagnose(caseItem.getStartLoc(),
diag::redundant_particular_literal_case)
.highlight(caseItem.getSourceRange());
TC.diagnose(cachedExpr->getLoc(),
diag::redundant_particular_literal_case_here)
.highlight(cachedExpr->getSourceRange());
continue;
}

if (!projection.isEmpty())
spaces.push_back(projection);
}
}

Expand Down
212 changes: 212 additions & 0 deletions validation-test/Sema/large-switch-rdar47365349.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// RUN: %target-typecheck-verify-swift

enum NumericBase {
case binary
case ternary
case quaternary
case quinary
case senary
case septary
case octal
case nonary
case decimal
case undecimal
case duodecimal
}

enum Direction {
case left
case right
}

enum WritingSystem {
case logographic
case alphabet(kind: Alphabet)
case abjad
case abugida
case syllabary
case other
}

enum Alphabet {
case roman
case greek
case cyrillic
}

func test(base: NumericBase, direction: Direction, writingSystem: WritingSystem) {
switch (base, direction, writingSystem) {
case (.binary, .left, .logographic),
(.binary, .left, .alphabet),
(.binary, .left, .abugida):
break

case (.binary, .right, .logographic),
(.binary, .right, .alphabet),
(.binary, .right, .abugida):
break

case (.binary, _, .abjad):
break

case (.binary, _, .syllabary):
break

case (.ternary, .left, .logographic):
break

case (.ternary, .left, .alphabet),
(.ternary, .left, .abugida):
break

case (.ternary, .right, .logographic),
(.ternary, .right, .abugida):
break

case (.ternary, .right, .alphabet):
break

case (.ternary, _, .abjad):
break

case (.ternary, _, .syllabary):
break

case (.quaternary, .left, .logographic):
break

case (.quaternary, .left, .alphabet),
(.quaternary, .left, .abugida):
break

case (.quaternary, .right, .logographic),
(.quaternary, .right, .abugida):
break

case (.quaternary, .right, .alphabet):
break

case (.quaternary, _, .abjad):
break

case (.quaternary, _, .syllabary):
break

case (.quinary, .left, .logographic),
(.senary, .left, .logographic):
break

case (.quinary, .left, .alphabet),
(.senary, .left, .alphabet),
(.quinary, .left, .abugida),
(.senary, .left, .abugida):
break

case (.quinary, .right, .logographic),
(.senary, .right, .logographic):
break

case (.quinary, .right, .alphabet),
(.senary, .right, .alphabet),
(.quinary, .right, .abugida),
(.senary, .right, .abugida):
break

case (.quinary, _, .abjad),
(.senary, _, .abjad):
break

case (.quinary, _, .syllabary),
(.senary, _, .syllabary):
break

case (.septary, .left, .logographic):
break

case (.septary, .left, .alphabet),
(.septary, .left, .abugida):
break

case (.septary, .right, .logographic):
break

case (.septary, .right, .alphabet),
(.septary, .right, .abugida):
break

case (.septary, _, .abjad):
break

case (.septary, _, .syllabary):
break

case (.decimal, .left, .logographic):
break

case (.decimal, .left, .alphabet),
(.decimal, .left, .abugida):
break

case (.decimal, .right, .logographic):
break

case (.decimal, .right, .alphabet),
(.decimal, .right, .abugida):
break

case (.octal, .left, .logographic),
(.nonary, .left, .logographic):
break

case (.octal, .left, .alphabet),
(.nonary, .left, .alphabet),
(.octal, .left, .abugida),
(.nonary, .left, .abugida):
break

case (.octal, .right, .logographic),
(.nonary, .right, .logographic):
break

case (.octal, .right, .alphabet),
(.nonary, .right, .alphabet),
(.octal, .right, .abugida),
(.nonary, .right, .abugida):
break

case (.octal, _, .abjad),
(.nonary, _, .abjad),
(.decimal, _, .abjad):
break

case (.octal, _, .syllabary),
(.nonary, _, .syllabary),
(.decimal, _, .syllabary):
break

case (.undecimal, .left, .logographic):
break

case (.undecimal, .left, .alphabet),
(.undecimal, .left, .abugida):
break

case (.undecimal, .right, .logographic):
break

case (.undecimal, .right, .alphabet),
(.undecimal, .right, .abugida):
break

case (.undecimal, _, .abjad):
break

case (.undecimal, _, .syllabary):
break

case (.duodecimal, _, _):
break
case (_, _, .other):
break
}
}