Skip to content

Sema: Reject empty switch statements during type checking so that we can issue fixits to fill the unhandled switch cases. #7766

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
merged 2 commits into from
Feb 25, 2017
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
4 changes: 0 additions & 4 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -975,10 +975,6 @@ ERROR(case_after_default,none,
"additional 'case' blocks cannot appear after the 'default' block of a 'switch'",
())

ERROR(empty_switch_stmt,none,
"'switch' statement body must have at least one 'case' or 'default' block",
())

// Case Stmt
ERROR(expected_case_where_expr,PointsToFirstBadToken,
"expected expression for 'where' guard of 'case'", ())
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2689,6 +2689,8 @@ ERROR(trailing_closure_requires_parens,none,
"trailing closure requires parentheses for disambiguation in this"
" context", ())

ERROR(empty_switch_stmt,none,
"'switch' statement body must have at least one 'case' or 'default' block",())
//------------------------------------------------------------------------------
// Type Check Patterns
//------------------------------------------------------------------------------
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsSema.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "swift/AST/DiagnosticsCommon.h"

namespace swift {
class SwitchStmt;
namespace diag {

/// Describes the kind of requirement in a protocol.
Expand All @@ -36,6 +37,8 @@ namespace swift {
extern detail::DiagWithArguments<void Signature>::type ID;
#include "DiagnosticsSema.def"
}
void diagnoseMissingCases(ASTContext &Context, const SwitchStmt *SwitchS,
Diagnostic Id);
}

#endif
1 change: 0 additions & 1 deletion include/swift/Subsystems.h
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,6 @@ namespace swift {
struct Implementation;
Implementation &Impl;
};

} // end namespace swift

#endif // SWIFT_SUBSYSTEMS_H
4 changes: 0 additions & 4 deletions lib/Parse/ParseStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2500,10 +2500,6 @@ ParserResult<Stmt> Parser::parseStmtSwitch(LabeledStmtInfo LabelInfo) {
Status.setIsParseError();
}

// Reject an empty 'switch'.
if (!DiagnosedNotCoveredStmt && cases.empty())
diagnose(rBraceLoc, diag::empty_switch_stmt);

return makeParserResult(
Status, SwitchStmt::create(LabelInfo, SwitchLoc, SubjectExpr.get(),
lBraceLoc, cases, rBraceLoc, Context));
Expand Down
89 changes: 3 additions & 86 deletions lib/SILOptimizer/Mandatory/DataflowDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,15 @@
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/ASTPrinter.h"
#include "swift/AST/DiagnosticEngine.h"
#include "swift/AST/DiagnosticsSema.h"
#include "swift/AST/DiagnosticsSIL.h"
#include "swift/AST/Expr.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SIL/SILInstruction.h"
#include "swift/SIL/SILLocation.h"
#include "swift/SIL/SILModule.h"
#include "swift/SIL/SILVisitor.h"
#include "swift/Syntax/TokenKinds.h"

using namespace swift;

Expand Down Expand Up @@ -59,89 +58,6 @@ static void diagnoseMissingReturn(const UnreachableInst *UI,
FLoc.isASTNode<ClosureExpr>() ? 1 : 0);
}


/// If the pattern handles an enum element, remove the enum element from the
/// given set. If seeing uncertain patterns, e.g. any pattern, return true;
/// otherwise return false.
static bool
removedHandledEnumElements(Pattern *P,
llvm::DenseSet<EnumElementDecl*> &UnhandledElements) {
if (auto *EEP = dyn_cast<EnumElementPattern>(P)) {
UnhandledElements.erase(EEP->getElementDecl());
} else if (auto *VP = dyn_cast<VarPattern>(P)) {
return removedHandledEnumElements(VP->getSubPattern(), UnhandledElements);
} else {
return true;
}
return false;
};

static void diagnoseMissingCases(ASTContext &Context,
const SwitchStmt *SwitchS) {
SourceLoc EndLoc = SwitchS->getEndLoc();
StringRef Placeholder = "<#Code#>";
SmallString<32> Buffer;
llvm::raw_svector_ostream OS(Buffer);

auto DefaultDiag = [&]() {
OS << tok::kw_default << ": " << Placeholder << "\n";
Context.Diags.diagnose(EndLoc, diag::non_exhaustive_switch).
fixItInsert(EndLoc, Buffer.str());
};
// To find the subject enum decl for this switch statement.
EnumDecl *SubjectED = nullptr;
if (auto SubjectTy = SwitchS->getSubjectExpr()->getType()) {
if (auto *ND = SubjectTy->getAnyNominal()) {
SubjectED = ND->getAsEnumOrEnumExtensionContext();
}
}

// The switch is not about an enum decl, add "default:" instead.
if (!SubjectED) {
DefaultDiag();
return;
}

// Assume enum elements are not handled in the switch statement.
llvm::DenseSet<EnumElementDecl*> UnhandledElements;

SubjectED->getAllElements(UnhandledElements);
for (auto Current : SwitchS->getCases()) {
// For each handled enum element, remove it from the bucket.
for (auto Item : Current->getCaseLabelItems()) {
if (removedHandledEnumElements(Item.getPattern(), UnhandledElements)) {
DefaultDiag();
return;
}
}
}

// No unhandled cases, so we are not sure the exact case to add, fall back to
// default instead.
if (UnhandledElements.empty()) {
DefaultDiag();
return;
}

// Sort the missing elements to a vector because set does not guarantee orders.
SmallVector<EnumElementDecl*, 4> SortedElements;
SortedElements.insert(SortedElements.begin(), UnhandledElements.begin(),
UnhandledElements.end());
std::sort(SortedElements.begin(),SortedElements.end(),
[](EnumElementDecl *LHS, EnumElementDecl *RHS) {
return LHS->getNameStr().compare(RHS->getNameStr()) < 0;
});

// Print each enum element name.
std::for_each(SortedElements.begin(), SortedElements.end(),
[&](EnumElementDecl *EE) {
OS << tok::kw_case << " ." << EE->getNameStr() << ": " <<
Placeholder << "\n";
});
Context.Diags.diagnose(EndLoc, diag::non_exhaustive_switch).
fixItInsert(EndLoc, Buffer.str());
}

static void diagnoseUnreachable(const SILInstruction *I,
ASTContext &Context) {
if (auto *UI = dyn_cast<UnreachableInst>(I)) {
Expand All @@ -168,7 +84,8 @@ static void diagnoseUnreachable(const SILInstruction *I,

// A non-exhaustive switch would also produce an unreachable instruction.
if (L.isASTNode<SwitchStmt>()) {
diagnoseMissingCases(Context, L.getAsASTNode<SwitchStmt>());
diagnoseMissingCases(Context, L.getAsASTNode<SwitchStmt>(),
diag::non_exhaustive_switch);
return;
}

Expand Down
86 changes: 86 additions & 0 deletions lib/Sema/TypeCheckStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
#include "TypeChecker.h"
#include "MiscDiagnostics.h"
#include "swift/Subsystems.h"
#include "swift/AST/ASTPrinter.h"
#include "swift/AST/ASTWalker.h"
#include "swift/AST/ASTVisitor.h"
#include "swift/AST/DiagnosticsSema.h"
#include "swift/AST/Identifier.h"
#include "swift/AST/Initializer.h"
#include "swift/AST/NameLookup.h"
Expand All @@ -29,6 +31,7 @@
#include "swift/Basic/Statistic.h"
#include "swift/Parse/Lexer.h"
#include "swift/Parse/LocalContext.h"
#include "swift/Syntax/TokenKinds.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/PointerUnion.h"
#include "llvm/ADT/SmallString.h"
Expand Down Expand Up @@ -193,6 +196,86 @@ namespace {
};
} // end anonymous namespace

/// If the pattern handles an enum element, remove the enum element from the
/// given set. If seeing uncertain patterns, e.g. any pattern, return true;
/// otherwise return false.
static bool
removedHandledEnumElements(Pattern *P,
llvm::DenseSet<EnumElementDecl*> &UnhandledElements) {
if (auto *EEP = dyn_cast<EnumElementPattern>(P)) {
UnhandledElements.erase(EEP->getElementDecl());
} else if (auto *VP = dyn_cast<VarPattern>(P)) {
return removedHandledEnumElements(VP->getSubPattern(), UnhandledElements);
} else {
return true;
}
return false;
};

void swift::diagnoseMissingCases(ASTContext &Context, const SwitchStmt *SwitchS,
Diagnostic Id) {
SourceLoc EndLoc = SwitchS->getEndLoc();
StringRef Placeholder = "<#Code#>";
SmallString<32> Buffer;
llvm::raw_svector_ostream OS(Buffer);

auto DefaultDiag = [&]() {
OS << tok::kw_default << ": " << Placeholder << "\n";
Context.Diags.diagnose(EndLoc, Id).fixItInsert(EndLoc, Buffer.str());
};
// To find the subject enum decl for this switch statement.
EnumDecl *SubjectED = nullptr;
if (auto SubjectTy = SwitchS->getSubjectExpr()->getType()) {
if (auto *ND = SubjectTy->getAnyNominal()) {
SubjectED = ND->getAsEnumOrEnumExtensionContext();
}
}

// The switch is not about an enum decl, add "default:" instead.
if (!SubjectED) {
DefaultDiag();
return;
}

// Assume enum elements are not handled in the switch statement.
llvm::DenseSet<EnumElementDecl*> UnhandledElements;

SubjectED->getAllElements(UnhandledElements);
for (auto Current : SwitchS->getCases()) {
// For each handled enum element, remove it from the bucket.
for (auto Item : Current->getCaseLabelItems()) {
if (removedHandledEnumElements(Item.getPattern(), UnhandledElements)) {
DefaultDiag();
return;
}
}
}

// No unhandled cases, so we are not sure the exact case to add, fall back to
// default instead.
if (UnhandledElements.empty()) {
DefaultDiag();
return;
}

// Sort the missing elements to a vector because set does not guarantee orders.
SmallVector<EnumElementDecl*, 4> SortedElements;
SortedElements.insert(SortedElements.begin(), UnhandledElements.begin(),
UnhandledElements.end());
std::sort(SortedElements.begin(),SortedElements.end(),
[](EnumElementDecl *LHS, EnumElementDecl *RHS) {
return LHS->getNameStr().compare(RHS->getNameStr()) < 0;
});

// Print each enum element name.
std::for_each(SortedElements.begin(), SortedElements.end(),
[&](EnumElementDecl *EE) {
OS << tok::kw_case << " ." << EE->getNameStr() << ": " <<
Placeholder << "\n";
});
Context.Diags.diagnose(EndLoc, Id).fixItInsert(EndLoc, Buffer.str());
}

static void setAutoClosureDiscriminators(DeclContext *DC, Stmt *S) {
S->walk(ContextualizeClosures(DC));
}
Expand Down Expand Up @@ -862,6 +945,9 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
AddSwitchNest switchNest(*this);
AddLabeledStmt labelNest(*this, S);

// Reject switch statements with empty blocks.
if (S->getCases().empty())
diagnoseMissingCases(TC.Context, S, diag::empty_switch_stmt);
for (unsigned i = 0, e = S->getCases().size(); i < e; ++i) {
auto *caseBlock = S->getCases()[i];
// Fallthrough transfers control to the next case block. In the
Expand Down
18 changes: 18 additions & 0 deletions test/FixCode/fixits-empty-switch.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// RUN: not %swift -typecheck -target %target-triple %s -emit-fixits-path %t.remap -I %S/Inputs
// RUN: c-arcmt-test %t.remap | arcmt-test -verify-transformed-files %s.result

enum E1 {
case e1
case e2
case e3
}

func foo1(_ e: E1) {
switch e {
}
}

func foo1 (_ i : Int) {
switch i {
}
}
22 changes: 22 additions & 0 deletions test/FixCode/fixits-empty-switch.swift.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// RUN: not %swift -typecheck -target %target-triple %s -emit-fixits-path %t.remap -I %S/Inputs
// RUN: c-arcmt-test %t.remap | arcmt-test -verify-transformed-files %s.result

enum E1 {
case e1
case e2
case e3
}

func foo1(_ e: E1) {
switch e {
case .e1: <#Code#>
case .e2: <#Code#>
case .e3: <#Code#>
}
}

func foo1 (_ i : Int) {
switch i {
default: <#Code#>
}
}
2 changes: 1 addition & 1 deletion test/Parse/invalid.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func testNotCoveredCase(x: Int) {
default:
break
#endif
}
} // expected-error{{'switch' statement body must have at least one 'case' or 'default' block}}
}

// rdar://18926814
Expand Down
4 changes: 2 additions & 2 deletions test/Parse/recovery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -219,11 +219,11 @@ func missingControllingExprInSwitch() {
case _: return
}

switch { 42 } { // expected-error {{expected expression in 'switch' statement}} expected-error{{all statements inside a switch must be covered by a 'case' or 'default'}} expected-error{{consecutive statements on a line must be separated by ';'}} {{16-16=;}} expected-error{{closure expression is unused}} expected-note{{did you mean to use a 'do' statement?}} {{17-17=do }}
switch { 42 } { // expected-error {{expected expression in 'switch' statement}} expected-error{{all statements inside a switch must be covered by a 'case' or 'default'}} expected-error{{consecutive statements on a line must be separated by ';'}} {{16-16=;}} expected-error{{closure expression is unused}} expected-note{{did you mean to use a 'do' statement?}} {{17-17=do }} // expected-error{{'switch' statement body must have at least one 'case' or 'default' block}}
case _: return // expected-error{{'case' label can only appear inside a 'switch' statement}}
}

switch { 42 }() { // expected-error {{expected expression in 'switch' statement}} expected-error {{all statements inside a switch must be covered by a 'case' or 'default'}} expected-error 2 {{consecutive statements on a line must be separated by ';'}} expected-error{{closure expression is unused}} expected-note{{did you mean to use a 'do' statement?}} {{19-19=do }}
switch { 42 }() { // expected-error {{expected expression in 'switch' statement}} expected-error {{all statements inside a switch must be covered by a 'case' or 'default'}} expected-error 2 {{consecutive statements on a line must be separated by ';'}} expected-error{{closure expression is unused}} expected-note{{did you mean to use a 'do' statement?}} {{19-19=do }} // expected-error{{'switch' statement body must have at least one 'case' or 'default' block}}
case _: return // expected-error{{'case' label can only appear inside a 'switch' statement}}
}
}
Expand Down
4 changes: 2 additions & 2 deletions test/Parse/switch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,12 @@ default: // expected-error{{additional 'case' blocks cannot appear after the 'de

switch x {
x = 1 // expected-error{{all statements inside a switch must be covered by a 'case' or 'default'}}
}
} // expected-error{{'switch' statement body must have at least one 'case' or 'default' block}}

switch x {
x = 1 // expected-error{{all statements inside a switch must be covered by a 'case' or 'default'}}
x = 2
}
} // expected-error{{'switch' statement body must have at least one 'case' or 'default' block}}

switch x {
default: // expected-error{{'default' label in a 'switch' should have at least one executable statement}} {{9-9= break}}
Expand Down