Merge pull request #10011 from CodaFi/code-covfeferage
[4.0] Add a size heuristic to the Space Engine
diff --git a/include/swift/AST/Attr.def b/include/swift/AST/Attr.def
index d96613b..22b0dbb 100644
--- a/include/swift/AST/Attr.def
+++ b/include/swift/AST/Attr.def
@@ -286,6 +286,11 @@
OnClass | NotSerialized | LongAttribute,
/*Not serialized */ 70)
+// HACK: Attribute needed to preserve source compatibility by downgrading errors
+// due to an SDK change in Dispatch
+SIMPLE_DECL_ATTR(_downgrade_exhaustivity_check, DowngradeExhaustivityCheck,
+ OnEnumElement | LongAttribute | UserInaccessible, 71)
+
#undef TYPE_ATTR
#undef DECL_ATTR_ALIAS
#undef SIMPLE_DECL_ATTR
diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def
index dd0c6a1..37a0add 100644
--- a/include/swift/AST/DiagnosticsSema.def
+++ b/include/swift/AST/DiagnosticsSema.def
@@ -3701,6 +3701,10 @@
WARNING(redundant_particular_case,none,
"case is already handled by previous patterns; consider removing it",())
+// HACK: Downgrades the above to warnings if any of the cases is marked
+// @_downgrade_exhaustivity_check.
+WARNING(non_exhaustive_switch_warn_swift3,none, "switch must be exhaustive", ())
+
#ifndef DIAG_NO_UNDEF
# if defined(DIAG)
# undef DIAG
diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp
index 66a9dde..71e7d99 100644
--- a/lib/AST/ASTDumper.cpp
+++ b/lib/AST/ASTDumper.cpp
@@ -840,6 +840,8 @@
void visitEnumElementDecl(EnumElementDecl *EED) {
printCommon(EED, "enum_element_decl");
+ if (EED->getAttrs().hasAttribute<DowngradeExhaustivityCheckAttr>())
+ OS << "@_downgrade_exhaustivity_check";
PrintWithColorRAII(OS, ParenthesisColor) << ')';
}
diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp
index e782469..bb065bb 100644
--- a/lib/AST/Attr.cpp
+++ b/lib/AST/Attr.cpp
@@ -506,6 +506,10 @@
Printer.printAttrName("@_staticInitializeObjCMetadata");
break;
+ case DAK_DowngradeExhaustivityCheck:
+ Printer.printAttrName("@_downgrade_exhaustivity_check");
+ break;
+
case DAK_Count:
llvm_unreachable("exceed declaration attribute kinds");
diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp
index 6c657b3..a43c95e 100644
--- a/lib/Sema/TypeCheckAttr.cpp
+++ b/lib/Sema/TypeCheckAttr.cpp
@@ -107,6 +107,7 @@
IGNORED_ATTR(NSKeyedArchiverClassName)
IGNORED_ATTR(StaticInitializeObjCMetadata)
IGNORED_ATTR(NSKeyedArchiverEncodeNonGenericSubclassesOnly)
+ IGNORED_ATTR(DowngradeExhaustivityCheck)
#undef IGNORED_ATTR
// @noreturn has been replaced with a 'Never' return type.
@@ -784,6 +785,7 @@
IGNORED_ATTR(ObjCMembers)
IGNORED_ATTR(StaticInitializeObjCMetadata)
IGNORED_ATTR(NSKeyedArchiverEncodeNonGenericSubclassesOnly)
+ IGNORED_ATTR(DowngradeExhaustivityCheck)
#undef IGNORED_ATTR
void visitAvailableAttr(AvailableAttr *attr);
diff --git a/lib/Sema/TypeCheckDecl.cpp b/lib/Sema/TypeCheckDecl.cpp
index 57892c3..40f2da3 100644
--- a/lib/Sema/TypeCheckDecl.cpp
+++ b/lib/Sema/TypeCheckDecl.cpp
@@ -6103,6 +6103,7 @@
UNINTERESTING_ATTR(NSKeyedArchiverClassName)
UNINTERESTING_ATTR(StaticInitializeObjCMetadata)
UNINTERESTING_ATTR(NSKeyedArchiverEncodeNonGenericSubclassesOnly)
+ UNINTERESTING_ATTR(DowngradeExhaustivityCheck)
#undef UNINTERESTING_ATTR
void visitAvailableAttr(AvailableAttr *attr) {
diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp
index 46cc777..f840484 100644
--- a/lib/Sema/TypeCheckProtocol.cpp
+++ b/lib/Sema/TypeCheckProtocol.cpp
@@ -5915,7 +5915,7 @@
/// Infer the attribute tostatic-initialize the Objective-C metadata for the
/// given class, if needed.
static void inferStaticInitializeObjCMetadata(ClassDecl *classDecl,
- bool requiresNSCodingAttr) {
+ bool requiresNSCodingAttr) {
// If we already have the attribute, there's nothing to do.
if (classDecl->getAttrs().hasAttribute<StaticInitializeObjCMetadataAttr>())
return;
diff --git a/lib/Sema/TypeCheckSwitchStmt.cpp b/lib/Sema/TypeCheckSwitchStmt.cpp
index 78b67b2..bfb590e 100644
--- a/lib/Sema/TypeCheckSwitchStmt.cpp
+++ b/lib/Sema/TypeCheckSwitchStmt.cpp
@@ -71,7 +71,7 @@
};
#define PAIRCASE(XS, YS) case PairSwitch(XS, YS)
-
+
class Space final {
private:
SpaceKind Kind;
@@ -79,12 +79,66 @@
Identifier Head;
std::forward_list<Space> Spaces;
+ // NB: This constant is arbitrary. Anecdotally, the Space Engine is
+ // capable of efficiently handling Spaces of around size 200, but it would
+ // potentially push an enormous fixit on the user.
+ static const size_t MAX_SPACE_SIZE = 128;
+
+ size_t computeSize(TypeChecker &TC,
+ SmallPtrSetImpl<TypeBase *> &cache) const {
+ switch (getKind()) {
+ case SpaceKind::Empty:
+ return 0;
+ case SpaceKind::BooleanConstant:
+ return 1;
+ case SpaceKind::Type: {
+ if (!canDecompose(getType())) {
+ return 1;
+ }
+ cache.insert(getType().getPointer());
+
+ SmallVector<Space, 4> spaces;
+ decompose(TC, getType(), spaces);
+ size_t acc = 0;
+ for (auto &sp : spaces) {
+ // Decomposed pattern spaces grow with the sum of the subspaces.
+ acc += sp.computeSize(TC, cache);
+ }
+
+ cache.erase(getType().getPointer());
+ return acc;
+ }
+ case SpaceKind::Constructor: {
+ size_t acc = 1;
+ for (auto &sp : getSpaces()) {
+ // Break self-recursive references among enum arguments.
+ if (sp.getKind() == SpaceKind::Type
+ && cache.count(sp.getType().getPointer())) {
+ continue;
+ }
+
+ // Constructor spaces grow with the product of their arguments.
+ acc *= sp.computeSize(TC, cache);
+ }
+ return acc;
+ }
+ case SpaceKind::Disjunct: {
+ size_t acc = 0;
+ for (auto &sp : getSpaces()) {
+ // Disjoint grow with the sum of the subspaces.
+ acc += sp.computeSize(TC, cache);
+ }
+ return acc;
+ }
+ }
+ }
+
public:
explicit Space(Type T)
: Kind(SpaceKind::Type), TypeAndVal(T, false), Head(Identifier()),
Spaces({}){}
- explicit Space(Type T, Identifier H, SmallVectorImpl<Space> &SP)
- : Kind(SpaceKind::Constructor), TypeAndVal(T, false), Head(H),
+ explicit Space(Type T, Identifier H, bool downgrade, SmallVectorImpl<Space> &SP)
+ : Kind(SpaceKind::Constructor), TypeAndVal(T, downgrade), Head(H),
Spaces(SP.begin(), SP.end()) {}
explicit Space(SmallVectorImpl<Space> &SP)
: Kind(SpaceKind::Disjunct), TypeAndVal(Type(), false),
@@ -100,7 +154,22 @@
void dump() const LLVM_ATTRIBUTE_USED;
+ size_t getSize(TypeChecker &TC) const {
+ SmallPtrSet<TypeBase *, 4> cache;
+ return computeSize(TC, cache);
+ }
+
+ static size_t getMaximumSize() {
+ return MAX_SPACE_SIZE;
+ }
+
bool isEmpty() const { return getKind() == SpaceKind::Empty; }
+
+ bool canDowngrade() const {
+ assert(getKind() == SpaceKind::Constructor
+ && "Wrong kind of space tried to access downgrade");
+ return TypeAndVal.getInt();
+ }
Type getType() const {
assert((getKind() == SpaceKind::Type
@@ -585,7 +654,8 @@
SmallVector<Space, 4> copyParams(this->getSpaces().begin(),
this->getSpaces().end());
copyParams[idx] = s1.minus(s2, TC);
- Space CS(this->getType(), this->Head, copyParams);
+ Space CS(this->getType(), this->getHead(), this->canDowngrade(),
+ copyParams);
constrSpaces.push_back(CS);
}
@@ -721,7 +791,7 @@
return Space();
}
}
- return Space(getType(), Head, simplifiedSpaces);
+ return Space(getType(), Head, canDowngrade(), simplifiedSpaces);
}
case SpaceKind::Type: {
// If the decomposition of a space is empty, the space is empty.
@@ -813,7 +883,10 @@
constElemSpaces.push_back(Space(TTy->getUnderlyingType()));
}
}
- return Space(tp, eed->getName(), constElemSpaces);
+ return Space(tp, eed->getName(),
+ eed->getAttrs()
+ .getAttribute<DowngradeExhaustivityCheckAttr>(),
+ constElemSpaces);
});
} else if (auto *TTy = tp->castTo<TupleType>()) {
// Decompose each of the elements into its component type space.
@@ -824,7 +897,8 @@
return Space(ty.getType());
});
// Create an empty constructor head for the tuple space.
- arr.push_back(Space(tp, Identifier(), constElemSpaces));
+ arr.push_back(Space(tp, Identifier(), /*canDowngrade*/false,
+ constElemSpaces));
} else {
llvm_unreachable("Can't decompose type?");
}
@@ -851,6 +925,8 @@
return;
}
+ bool sawDowngradablePattern = false;
+ bool sawRedundantPattern = false;
SmallVector<Space, 4> spaces;
for (unsigned i = 0, e = Switch->getCases().size(); i < e; ++i) {
auto *caseBlock = Switch->getCases()[i];
@@ -864,9 +940,12 @@
if (caseItem.isDefault())
return;
- auto projection = projectPattern(TC, caseItem.getPattern());
+ auto projection = projectPattern(TC, caseItem.getPattern(),
+ sawDowngradablePattern);
if (projection.isUseful()
&& projection.isSubspace(Space(spaces), TC)) {
+ sawRedundantPattern |= true;
+
TC.diagnose(caseItem.getStartLoc(),
diag::redundant_particular_case)
.highlight(caseItem.getSourceRange());
@@ -874,9 +953,26 @@
spaces.push_back(projection);
}
}
-
+
Space totalSpace(Switch->getSubjectExpr()->getType());
Space coveredSpace(spaces);
+ size_t totalSpaceSize = totalSpace.getSize(TC);
+ if (totalSpaceSize > Space::getMaximumSize()) {
+ // Because the space is large, we have to extend the size
+ // heuristic to compensate for actually exhaustively pattern matching
+ // over enormous spaces. In this case, if the covered space covers
+ // as much as the total space, and there were no duplicates, then we
+ // can assume the user did the right thing and that they don't need
+ // a 'default' to be inserted.
+ if (!sawRedundantPattern
+ && coveredSpace.getSize(TC) >= totalSpaceSize) {
+ return;
+ }
+
+ diagnoseMissingCases(TC, Switch, /*justNeedsDefault*/true, Space());
+ return;
+ }
+
auto uncovered = totalSpace.minus(coveredSpace, TC).simplify(TC);
if (uncovered.isEmpty()) {
return;
@@ -904,43 +1000,81 @@
uncovered = Space(spaces);
}
- diagnoseMissingCases(TC, Switch, /*justNeedsDefault*/ false, uncovered);
+ diagnoseMissingCases(TC, Switch, /*justNeedsDefault*/ false, uncovered,
+ sawDowngradablePattern);
+ }
+
+ // HACK: Search the space for any remaining cases that were labelled
+ // @_downgrade_exhaustivity_check.
+ static bool shouldDowngradeToWarning(const Space &masterSpace) {
+ switch (masterSpace.getKind()) {
+ case SpaceKind::Type:
+ case SpaceKind::BooleanConstant:
+ case SpaceKind::Empty:
+ return false;
+ // Traverse the constructor and its subspaces.
+ case SpaceKind::Constructor:
+ return masterSpace.canDowngrade()
+ || std::accumulate(masterSpace.getSpaces().begin(),
+ masterSpace.getSpaces().end(),
+ false,
+ [](bool acc, const Space &space) {
+ return acc || shouldDowngradeToWarning(space);
+ });
+ case SpaceKind::Disjunct:
+ // Traverse the disjunct's subspaces.
+ return std::accumulate(masterSpace.getSpaces().begin(),
+ masterSpace.getSpaces().end(),
+ false,
+ [](bool acc, const Space &space) {
+ return acc || shouldDowngradeToWarning(space);
+ });
+ }
}
- static void diagnoseMissingCases(TypeChecker &TC, const SwitchStmt *Switch,
- bool JustNeedsDefault,
- SpaceEngine::Space uncovered) {
- bool Empty = Switch->getCases().empty();
- SourceLoc StartLoc = Switch->getStartLoc();
- SourceLoc EndLoc = Switch->getEndLoc();
- StringRef Placeholder = getCodePlaceholder();
- llvm::SmallString<128> Buffer;
- llvm::raw_svector_ostream OS(Buffer);
+ static void diagnoseMissingCases(TypeChecker &TC, const SwitchStmt *SS,
+ bool justNeedsDefault,
+ Space uncovered,
+ bool sawDowngradablePattern = false) {
+ SourceLoc startLoc = SS->getStartLoc();
+ SourceLoc endLoc = SS->getEndLoc();
+ StringRef placeholder = getCodePlaceholder();
+ llvm::SmallString<128> buffer;
+ llvm::raw_svector_ostream OS(buffer);
bool InEditor = TC.Context.LangOpts.DiagnosticsEditorMode;
- if (JustNeedsDefault) {
- OS << tok::kw_default << ":\n" << Placeholder << "\n";
- if (Empty) {
- TC.Context.Diags.diagnose(StartLoc, diag::empty_switch_stmt)
- .fixItInsert(EndLoc, Buffer.str());
+ if (justNeedsDefault) {
+ OS << tok::kw_default << ":\n" << placeholder << "\n";
+ if (SS->getCases().empty()) {
+ TC.Context.Diags.diagnose(startLoc, diag::empty_switch_stmt)
+ .fixItInsert(endLoc, buffer.str());
} else {
- TC.Context.Diags.diagnose(StartLoc, diag::non_exhaustive_switch);
- TC.Context.Diags.diagnose(StartLoc, diag::missing_several_cases,
- uncovered.isEmpty()).fixItInsert(EndLoc,
- Buffer.str());
+ TC.Context.Diags.diagnose(startLoc, diag::non_exhaustive_switch);
+ TC.Context.Diags.diagnose(startLoc, diag::missing_several_cases,
+ uncovered.isEmpty()).fixItInsert(endLoc,
+ buffer.str());
}
return;
}
// If there's nothing else to diagnose, bail.
if (uncovered.isEmpty()) return;
-
+
+ auto mainDiagType = diag::non_exhaustive_switch;
+ if (TC.Context.isSwiftVersion3()) {
+ if (!sawDowngradablePattern && shouldDowngradeToWarning(uncovered)) {
+ mainDiagType = diag::non_exhaustive_switch_warn_swift3;
+ }
+ }
+
// If editing is enabled, emit a formatted error of the form:
//
// switch must be exhaustive, do you want to add missing cases?
- // case (.none, .some(_)): <#code#>
- // case (.some(_), .none): <#code#>
+ // case (.none, .some(_)):
+ // <#code#>
+ // case (.some(_), .none):
+ // <#code#>
//
// else:
//
@@ -949,7 +1083,7 @@
// missing case '(.none, .some(_))'
// missing case '(.some(_), .none)'
if (InEditor) {
- Buffer.clear();
+ buffer.clear();
SmallVector<Space, 8> emittedSpaces;
for (auto &uncoveredSpace : uncovered.getSpaces()) {
SmallVector<Space, 4> flats;
@@ -961,17 +1095,18 @@
OS << tok::kw_case << " ";
flat.show(OS);
- OS << ":\n" << Placeholder << "\n";
+ OS << ":\n" << placeholder << "\n";
emittedSpaces.push_back(flat);
}
}
- TC.diagnose(StartLoc, diag::non_exhaustive_switch);
- TC.diagnose(StartLoc, diag::missing_several_cases, false)
- .fixItInsert(EndLoc, Buffer.str());
+ TC.diagnose(startLoc, diag::non_exhaustive_switch);
+ TC.diagnose(startLoc, diag::missing_several_cases, false)
+ .fixItInsert(endLoc, buffer.str());
+
} else {
- TC.Context.Diags.diagnose(StartLoc, diag::non_exhaustive_switch);
+ TC.Context.Diags.diagnose(startLoc, mainDiagType);
SmallVector<Space, 8> emittedSpaces;
for (auto &uncoveredSpace : uncovered.getSpaces()) {
@@ -982,9 +1117,9 @@
continue;
}
- Buffer.clear();
+ buffer.clear();
flat.show(OS);
- TC.diagnose(StartLoc, diag::missing_particular_case, Buffer.str());
+ TC.diagnose(startLoc, diag::missing_particular_case, buffer.str());
emittedSpaces.push_back(flat);
}
@@ -1006,7 +1141,7 @@
flats.push_back(space);
return;
}
-
+
// To recursively recover a pattern matrix from a bunch of disjuncts:
// 1) Unpack the arguments to the constructor under scrutiny.
// 2) Traverse each argument in turn.
@@ -1022,17 +1157,13 @@
SmallVector<Space, 4> columnVect;
flatten(subspace, columnVect);
- // Pattern matrices grow quasi-factorially in the size of the
- // input space.
- multiplier *= columnVect.size();
-
size_t startSize = matrix.size();
if (!matrix.empty() && columnVect.size() > 1) {
size_t oldCount = matrix.size();
- matrix.reserve(multiplier * oldCount);
+ matrix.reserve(oldCount * columnVect.size());
// Indexing starts at 1, we already have 'startSize'-many elements
// in the matrix; multiplies by 1 are no-ops.
- for (size_t i = 1; i < multiplier; ++i) {
+ for (size_t i = 1; i < columnVect.size(); ++i) {
std::copy_n(matrix.begin(), oldCount, std::back_inserter(matrix));
}
}
@@ -1067,11 +1198,16 @@
matrix[rowIdx].push_back(columnVect[colIdx]);
}
}
+
+ // Pattern matrices grow quasi-factorially in the size of the
+ // input space.
+ multiplier *= columnVect.size();
}
// Wrap the matrix rows into this constructor.
for (auto &row : matrix) {
- flats.push_back(Space(space.getType(), space.getHead(), row));
+ flats.push_back(Space(space.getType(), space.getHead(),
+ space.canDowngrade(), row));
}
}
break;
@@ -1090,7 +1226,8 @@
}
// Recursively project a pattern into a Space.
- static Space projectPattern(TypeChecker &TC, const Pattern *item) {
+ static Space projectPattern(TypeChecker &TC, const Pattern *item,
+ bool &sawDowngradablePattern) {
switch (item->getKind()) {
case PatternKind::Any:
case PatternKind::Named:
@@ -1122,28 +1259,39 @@
return Space();
case PatternKind::Var: {
auto *VP = cast<VarPattern>(item);
- return projectPattern(TC, VP->getSubPattern());
+ return projectPattern(TC, VP->getSubPattern(), sawDowngradablePattern);
}
case PatternKind::Paren: {
auto *PP = cast<ParenPattern>(item);
- return projectPattern(TC, PP->getSubPattern());
+ return projectPattern(TC, PP->getSubPattern(), sawDowngradablePattern);
}
case PatternKind::OptionalSome: {
auto *OSP = cast<OptionalSomePattern>(item);
SmallVector<Space, 1> payload = {
- projectPattern(TC, OSP->getSubPattern())
+ projectPattern(TC, OSP->getSubPattern(), sawDowngradablePattern)
};
- return Space(item->getType(), TC.Context.getIdentifier("some"), payload);
+ return Space(item->getType(), TC.Context.getIdentifier("some"), /*canDowngrade*/false,
+ payload);
}
case PatternKind::EnumElement: {
auto *VP = cast<EnumElementPattern>(item);
TC.validateDecl(item->getType()->getEnumOrBoundGenericEnum());
+
+ bool canDowngrade = false;
+ if (auto *eed = VP->getElementDecl()) {
+ if (eed->getAttrs().getAttribute<DowngradeExhaustivityCheckAttr>()) {
+ canDowngrade |= true;
+ sawDowngradablePattern |= true;
+ }
+ }
+
SmallVector<Space, 4> conArgSpace;
auto *SP = VP->getSubPattern();
if (!SP) {
// If there's no sub-pattern then there's no further recursive
// structure here. Yield the constructor space.
- return Space(item->getType(), VP->getName(), conArgSpace);
+ return Space(item->getType(), VP->getName(), canDowngrade,
+ conArgSpace);
}
switch (SP->getKind()) {
@@ -1152,9 +1300,11 @@
std::transform(TP->getElements().begin(), TP->getElements().end(),
std::back_inserter(conArgSpace),
[&](TuplePatternElt pate) {
- return projectPattern(TC, pate.getPattern());
+ return projectPattern(TC, pate.getPattern(),
+ sawDowngradablePattern);
});
- return Space(item->getType(), VP->getName(), conArgSpace);
+ return Space(item->getType(), VP->getName(), /*canDowngrade*/false,
+ conArgSpace);
}
case PatternKind::Paren: {
auto *PP = dyn_cast<ParenPattern>(SP);
@@ -1171,15 +1321,21 @@
conArgSpace.push_back(Space(ty.getType()));
}
} else {
- conArgSpace.push_back(projectPattern(TC, SP));
+ conArgSpace.push_back(projectPattern(TC, SP,
+ sawDowngradablePattern));
}
} else {
- conArgSpace.push_back(projectPattern(TC, SP));
+ conArgSpace.push_back(projectPattern(TC, SP,
+ sawDowngradablePattern));
}
- return Space(item->getType(), VP->getName(), conArgSpace);
+ // FIXME: This isn't *technically* correct, but we only use the
+ // downgradability of the master space, not the projected space,
+ // when reconstructing the missing pattern matrix.
+ return Space(item->getType(), VP->getName(), /*canDowngrade*/false,
+ conArgSpace);
}
default:
- return projectPattern(TC, SP);
+ return projectPattern(TC, SP, sawDowngradablePattern);
}
}
case PatternKind::Tuple: {
@@ -1188,9 +1344,10 @@
std::transform(TP->getElements().begin(), TP->getElements().end(),
std::back_inserter(conArgSpace),
[&](TuplePatternElt pate) {
- return projectPattern(TC, pate.getPattern());
+ return projectPattern(TC, pate.getPattern(), sawDowngradablePattern);
});
- return Space(item->getType(), Identifier(), conArgSpace);
+ return Space(item->getType(), Identifier(), /*canDowngrade*/false,
+ conArgSpace);
}
}
}
diff --git a/test/SILGen/downgrade_exhaustivity_swift3.swift b/test/SILGen/downgrade_exhaustivity_swift3.swift
new file mode 100644
index 0000000..f0be6c1
--- /dev/null
+++ b/test/SILGen/downgrade_exhaustivity_swift3.swift
@@ -0,0 +1,48 @@
+// RUN: %target-swift-frontend -swift-version 3 -emit-silgen %s | %FileCheck %s
+
+enum Downgradable {
+ case spoon
+ case hat
+ @_downgrade_exhaustivity_check
+ case fork
+}
+
+// CHECK-LABEL: sil hidden @_T029downgrade_exhaustivity_swift343testDowngradableOmittedPatternIsUnreachableyAA0E0OSg3pat_tF
+func testDowngradableOmittedPatternIsUnreachable(pat : Downgradable?) {
+ // CHECK: switch_enum {{%.*}} : $Downgradable, case #Downgradable.spoon!enumelt: [[CASE1:bb[0-9]+]], case #Downgradable.hat!enumelt: [[CASE2:bb[0-9]+]], default [[DEFAULT_CASE:bb[0-9]+]]
+ switch pat! {
+ // CHECK: [[CASE1]]:
+ case .spoon:
+ break
+ // CHECK: [[CASE2]]:
+ case .hat:
+ break
+ // CHECK: [[DEFAULT_CASE]]:
+ // CHECK-NEXT: unreachable
+ }
+
+ // CHECK: switch_enum {{%[0-9]+}} : $Downgradable, case #Downgradable.spoon!enumelt: {{bb[0-9]+}}, case #Downgradable.hat!enumelt: {{bb[0-9]+}}, default [[TUPLE_DEFAULT_CASE_1:bb[0-9]+]]
+ // CHECK: switch_enum [[Y:%[0-9]+]] : $Downgradable, case #Downgradable.spoon!enumelt: {{bb[0-9]+}}, case #Downgradable.hat!enumelt: {{bb[0-9]+}}, default [[TUPLE_DEFAULT_CASE_2:bb[0-9]+]]
+ switch (pat!, pat!) {
+ case (.spoon, .spoon):
+ break
+ case (.spoon, .hat):
+ break
+ case (.hat, .spoon):
+ break
+ case (.hat, .hat):
+ break
+ // CHECK: [[TUPLE_DEFAULT_CASE_2]]:
+ // CHECK-NEXT: unreachable
+
+ // CHECK: switch_enum [[Y]] : $Downgradable, case #Downgradable.spoon!enumelt: {{bb[0-9]+}}, case #Downgradable.hat!enumelt: {{bb[0-9]+}}, default [[TUPLE_DEFAULT_CASE_3:bb[0-9]+]]
+
+ // CHECK: [[TUPLE_DEFAULT_CASE_3]]:
+ // CHECK-NEXT: unreachable
+
+ // CHECK: [[TUPLE_DEFAULT_CASE_1]]:
+ // CHECK-NEXT: unreachable
+ }
+
+}
+
diff --git a/test/Sema/exhaustive_switch.swift b/test/Sema/exhaustive_switch.swift
index 0ea73f2..24c0200 100644
--- a/test/Sema/exhaustive_switch.swift
+++ b/test/Sema/exhaustive_switch.swift
@@ -329,7 +329,7 @@
case (.hat, .spoon):
break
}
-
+
switch (x!, x!) { // expected-error {{switch must be exhaustive}}
// expected-note@-1 {{add missing case: '(.fork, _)'}}
// expected-note@-2 {{add missing case: '(.hat, .spoon)'}}
@@ -343,3 +343,158 @@
break
}
}
+
+enum LargeSpaceEnum {
+ case case0
+ case case1
+ case case2
+ case case3
+ case case4
+ case case5
+ case case6
+ case case7
+ case case8
+ case case9
+ case case10
+}
+
+func notQuiteBigEnough() -> Bool {
+ switch (LargeSpaceEnum.case1, LargeSpaceEnum.case2) { // expected-error {{switch must be exhaustive}}
+ // expected-note@-1 110 {{add missing case:}}
+ case (.case0, .case0): return true
+ case (.case1, .case1): return true
+ case (.case2, .case2): return true
+ case (.case3, .case3): return true
+ case (.case4, .case4): return true
+ case (.case5, .case5): return true
+ case (.case6, .case6): return true
+ case (.case7, .case7): return true
+ case (.case8, .case8): return true
+ case (.case9, .case9): return true
+ case (.case10, .case10): return true
+ }
+}
+
+enum OverlyLargeSpaceEnum {
+ case case0
+ case case1
+ case case2
+ case case3
+ case case4
+ case case5
+ case case6
+ case case7
+ case case8
+ case case9
+ case case10
+ case case11
+}
+
+func quiteBigEnough() -> Bool {
+ switch (OverlyLargeSpaceEnum.case1, OverlyLargeSpaceEnum.case2) { // expected-error {{switch must be exhaustive}}
+ // expected-note@-1 {{do you want to add a default clause?}}
+ case (.case0, .case0): return true
+ case (.case1, .case1): return true
+ case (.case2, .case2): return true
+ case (.case3, .case3): return true
+ case (.case4, .case4): return true
+ case (.case5, .case5): return true
+ case (.case6, .case6): return true
+ case (.case7, .case7): return true
+ case (.case8, .case8): return true
+ case (.case9, .case9): return true
+ case (.case10, .case10): return true
+ case (.case11, .case11): return true
+ }
+
+ // No diagnostic
+ switch (OverlyLargeSpaceEnum.case1, OverlyLargeSpaceEnum.case2) { // expected-error {{switch must be exhaustive}}
+ // expected-note@-1 {{do you want to add a default clause?}}
+ case (.case0, _): return true
+ case (.case1, _): return true
+ case (.case2, _): return true
+ case (.case3, _): return true
+ case (.case4, _): return true
+ case (.case5, _): return true
+ case (.case6, _): return true
+ case (.case7, _): return true
+ case (.case8, _): return true
+ case (.case9, _): return true
+ case (.case10, _): return true
+ }
+
+
+ // No diagnostic
+ switch (OverlyLargeSpaceEnum.case1, OverlyLargeSpaceEnum.case2) {
+ case (.case0, _): return true
+ case (.case1, _): return true
+ case (.case2, _): return true
+ case (.case3, _): return true
+ case (.case4, _): return true
+ case (.case5, _): return true
+ case (.case6, _): return true
+ case (.case7, _): return true
+ case (.case8, _): return true
+ case (.case9, _): return true
+ case (.case10, _): return true
+ case (.case11, _): return true
+ }
+
+ // No diagnostic
+ switch (OverlyLargeSpaceEnum.case1, OverlyLargeSpaceEnum.case2) {
+ case (_, .case0): return true
+ case (_, .case1): return true
+ case (_, .case2): return true
+ case (_, .case3): return true
+ case (_, .case4): return true
+ case (_, .case5): return true
+ case (_, .case6): return true
+ case (_, .case7): return true
+ case (_, .case8): return true
+ case (_, .case9): return true
+ case (_, .case10): return true
+ case (_, .case11): return true
+ }
+
+ // No diagnostic
+ switch (OverlyLargeSpaceEnum.case1, OverlyLargeSpaceEnum.case2) {
+ case (_, _): return true
+ }
+
+ // No diagnostic
+ switch (OverlyLargeSpaceEnum.case1, OverlyLargeSpaceEnum.case2) {
+ case (.case0, .case0): return true
+ case (.case1, .case1): return true
+ case (.case2, .case2): return true
+ case (.case3, .case3): return true
+ case _: return true
+ }
+}
+
+indirect enum InfinitelySized {
+ case one
+ case two
+ case recur(InfinitelySized)
+ case mutualRecur(MutuallyRecursive, InfinitelySized)
+}
+
+indirect enum MutuallyRecursive {
+ case one
+ case two
+ case recur(MutuallyRecursive)
+ case mutualRecur(InfinitelySized, MutuallyRecursive)
+}
+
+func infinitelySized() -> Bool {
+ switch (InfinitelySized.one, InfinitelySized.one) { // expected-error {{switch must be exhaustive}}
+ // expected-note@-1 10 {{add missing case:}}
+ case (.one, .one): return true
+ case (.two, .two): return true
+ }
+
+ switch (MutuallyRecursive.one, MutuallyRecursive.one) { // expected-error {{switch must be exhaustive}}
+ // expected-note@-1 10 {{add missing case:}}
+ case (.one, .one): return true
+ case (.two, .two): return true
+ }
+}
diff --git a/test/attr/attr_downgrade_exhaustivity.swift b/test/attr/attr_downgrade_exhaustivity.swift
new file mode 100644
index 0000000..c357209
--- /dev/null
+++ b/test/attr/attr_downgrade_exhaustivity.swift
@@ -0,0 +1,150 @@
+// RUN: %target-typecheck-verify-swift -swift-version 3 %s
+
+enum Runcible {
+ case spoon
+ case hat
+ @_downgrade_exhaustivity_check
+ case fork
+}
+
+enum Trioptional {
+ case some(Runcible)
+ case just(Runcible)
+ case none
+}
+
+enum Fungible {
+ case cash
+ case giftCard
+}
+
+func missingCases(x: Runcible?, y: Runcible?, z: Trioptional) {
+ // Should warn in S3 mode that `fork` isn't used
+ switch x! { // expected-warning {{switch must be exhaustive}}
+ // expected-note@-1 {{add missing case: '.fork'}}
+ case .spoon:
+ break
+ case .hat:
+ break
+ }
+
+ // Should warn in S3 mode that `fork` isn't used
+ switch (x!, y!) { // expected-warning {{switch must be exhaustive}}
+ // expected-note@-1 3 {{add missing case:}}
+ case (.spoon, .spoon):
+ break
+ case (.spoon, .hat):
+ break
+ case (.hat, .spoon):
+ break
+ case (.hat, .hat):
+ break
+ }
+
+ // Should error, since `fork` is used but not totally covered
+ switch (x!, y!) { // expected-error {{switch must be exhaustive}}
+ // expected-note@-1 {{add missing case: '(.fork, .hat)'}}
+ // expected-note@-2 {{add missing case: '(.fork, .fork)'}}
+ // expected-note@-3 {{add missing case: '(.hat, .fork)'}}
+ // expected-note@-4 {{add missing case: '(_, .fork)'}}
+ case (.spoon, .spoon):
+ break
+ case (.spoon, .hat):
+ break
+ case (.hat, .spoon):
+ break
+ case (.hat, .hat):
+ break
+ case (.fork, .spoon):
+ break
+ }
+
+ // Should error, since `fork` is used but not totally covered
+ switch (x!, y!) { // expected-error {{switch must be exhaustive}}
+ // expected-note@-1 {{add missing case: '(.fork, _)'}}
+ // expected-note@-2 {{add missing case: '(.spoon, .fork)'}}
+ case (.spoon, .spoon):
+ break
+ case (.spoon, .hat):
+ break
+ case (.hat, .spoon):
+ break
+ case (.hat, .hat):
+ break
+ case (.hat, .fork):
+ break
+ }
+
+ // Should warn in S3 mode that `fork` isn't used
+ switch x { // expected-warning {{switch must be exhaustive}}
+ // expected-note@-1 {{add missing case: '.some(.fork)'}}
+ case .some(.spoon):
+ break
+ case .some(.hat):
+ break
+ case .none:
+ break
+ }
+
+ // Should warn in S3 mode that `fork` isn't used
+ switch (x, y!) { // expected-warning {{switch must be exhaustive}}
+ // expected-note@-1 {{add missing case: '(.some(.fork), _)'}}
+ // expected-note@-2 {{add missing case: '(.none, .fork)'}}
+ // expected-note@-3 {{add missing case: '(.some(.hat), .fork)'}}
+ // expected-note@-4 {{add missing case: '(_, .fork)'}}
+ case (.some(.spoon), .spoon):
+ break
+ case (.some(.spoon), .hat):
+ break
+ case (.some(.hat), .spoon):
+ break
+ case (.some(.hat), .hat):
+ break
+ case (.none, .spoon):
+ break
+ case (.none, .hat):
+ break
+ }
+
+ // Should warn in S3 mode that `fork` isn't used
+ switch (x, y) { // expected-warning {{switch must be exhaustive}}
+ // expected-note@-1 {{add missing case: '(.some(.fork), _)'}}
+ // expected-note@-2 {{add missing case: '(_, .some(.fork))'}}
+ // expected-note@-3 {{add missing case: '(.some(.hat), .some(.fork))'}}
+ // expected-note@-4 {{add missing case: '(.none, _)'}}
+ case (.some(.spoon), .some(.spoon)):
+ break
+ case (.some(.spoon), .some(.hat)):
+ break
+ case (.some(.spoon), .none):
+ break
+ case (.some(.hat), .some(.spoon)):
+ break
+ case (.some(.hat), .some(.hat)):
+ break
+ case (.some(.hat), .none):
+ break
+ case (.some(.hat), .some(.spoon)): // expected-warning {{case is already handled by previous patterns; consider removing it}}
+ break
+ case (.some(.hat), .some(.hat)): // expected-warning {{case is already handled by previous patterns; consider removing it}}
+ break
+ case (.some(.hat), .none): // expected-warning {{case is already handled by previous patterns; consider removing it}}
+ break
+ }
+
+ // Should warn in S3 mode that `fork` isn't used
+ switch z { // expected-warning {{switch must be exhaustive}}
+ // expected-note@-1 {{add missing case: '.some(.fork)'}}
+ // expected-note@-2 {{add missing case: '.just(.fork)'}}
+ case .some(.spoon):
+ break
+ case .some(.hat):
+ break
+ case .just(.spoon):
+ break
+ case .just(.hat):
+ break
+ case .none:
+ break
+ }
+}
diff --git a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp
index f7b1d8a..d2712da 100644
--- a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp
+++ b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp
@@ -729,6 +729,7 @@
// Ignore these.
case DAK_ShowInInterface:
case DAK_RawDocComment:
+ case DAK_DowngradeExhaustivityCheck:
continue;
default:
break;