Skip to content
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

[Sema/SILGen] Support for fallthrough into cases with pattern variables. #14041

Merged
merged 1 commit into from
Jan 23, 2018
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 4 additions & 2 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2889,14 +2889,16 @@ ERROR(fallthrough_outside_switch,none,
ERROR(fallthrough_from_last_case,none,
"'fallthrough' without a following 'case' or 'default' block", ())
ERROR(fallthrough_into_case_with_var_binding,none,
"'fallthrough' cannot transfer control to a case label that declares variables",
())
"'fallthrough' from a case which doesn't bind variable %0",
(Identifier))

ERROR(unnecessary_cast_over_optionset,none,
"unnecessary cast over raw value of %0", (Type))

ERROR(type_mismatch_multiple_pattern_list,none,
"pattern variable bound to type %0, expected type %1", (Type, Type))
ERROR(type_mismatch_fallthrough_pattern_list,none,
"pattern variable bound to type %0, fallthrough case bound to type %1", (Type, Type))

WARNING(where_on_one_item, none,
"'where' only applies to the second pattern match in this case", ())
Expand Down
15 changes: 13 additions & 2 deletions include/swift/AST/Stmt.h
Original file line number Diff line number Diff line change
Expand Up @@ -1052,18 +1052,29 @@ class ContinueStmt : public Stmt {
/// FallthroughStmt - The keyword "fallthrough".
class FallthroughStmt : public Stmt {
SourceLoc Loc;
CaseStmt *FallthroughSource;
CaseStmt *FallthroughDest;

public:
FallthroughStmt(SourceLoc Loc, Optional<bool> implicit = None)
: Stmt(StmtKind::Fallthrough, getDefaultImplicitFlag(implicit, Loc)),
Loc(Loc), FallthroughDest(nullptr)
Loc(Loc), FallthroughSource(nullptr), FallthroughDest(nullptr)
{}

SourceLoc getLoc() const { return Loc; }

SourceRange getSourceRange() const { return Loc; }


/// Get the CaseStmt block from which the fallthrough transfers control.
/// Set during Sema. (May stay null if fallthrough is invalid.)
CaseStmt *getFallthroughSource() const {
return FallthroughSource;
}
void setFallthroughSource(CaseStmt *C) {
assert(!FallthroughSource && "fallthrough source already set?!");
FallthroughSource = C;
}

/// Get the CaseStmt block to which the fallthrough transfers control.
/// Set during Sema.
CaseStmt *getFallthroughDest() const {
Expand Down
64 changes: 56 additions & 8 deletions lib/SILGen/SILGenPattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1075,7 +1075,8 @@ void PatternMatchEmission::emitWildcardDispatch(ClauseMatrix &clauses,

bool hasMultipleItems = false;
if (auto *caseStmt = dyn_cast<CaseStmt>(stmt)) {
hasMultipleItems = caseStmt->getCaseLabelItems().size() > 1;
hasMultipleItems = clauses[row].hasFallthroughTo() ||
caseStmt->getCaseLabelItems().size() > 1;
}

// Bind the rest of the patterns.
Expand Down Expand Up @@ -2383,7 +2384,20 @@ void PatternMatchEmission::emitAddressOnlyAllocations() {
return;

SILType ty = SGF.getLoweredType(V->getType());

if (ty.isNull()) {
// If we're making the shared block on behalf of a previous case's
// fallthrough, caseBlock's VarDecl's won't be in the SGF yet, so
// determine phi types by using current vars of the same name.
for (auto var : SGF.VarLocs) {
auto varDecl = dyn_cast<VarDecl>(var.getFirst());
if (varDecl && varDecl->hasName() && varDecl->getName() == V->getName()) {
ty = var.getSecond().value->getType();
if (var.getSecond().box) {
ty = ty.getObjectType();
}
}
}
}
if (ty.isAddressOnly(SGF.F.getModule())) {
assert(!Temporaries[V]);
Temporaries[V] = SGF.emitTemporaryAllocation(V, ty);
Expand Down Expand Up @@ -2617,7 +2631,7 @@ void SILGenFunction::emitSwitchStmt(SwitchStmt *S) {
// statement.
JumpDest sharedDest = emission.getSharedCaseBlockDest(caseBlock);
Cleanups.emitBranchAndCleanups(sharedDest, caseBlock);
} else if (caseBlock->getCaseLabelItems().size() > 1) {
} else if (row.hasFallthroughTo() || caseBlock->getCaseLabelItems().size() > 1) {
JumpDest sharedDest =
emission.getSharedCaseBlockDest(caseBlock);

Expand All @@ -2630,15 +2644,15 @@ void SILGenFunction::emitSwitchStmt(SwitchStmt *S) {
ArrayRef<CaseLabelItem> labelItems = caseBlock->getCaseLabelItems();
SmallVector<SILValue, 4> args;
SmallVector<VarDecl *, 4> expectedVarOrder;
SmallVector<VarDecl *, 4> Vars;
SmallVector<VarDecl *, 4> vars;
labelItems[0].getPattern()->collectVariables(expectedVarOrder);
row.getCasePattern()->collectVariables(Vars);
row.getCasePattern()->collectVariables(vars);

SILModule &M = F.getModule();
for (auto expected : expectedVarOrder) {
if (!expected->hasName())
continue;
for (auto var : Vars) {
for (auto *var : vars) {
if (var->hasName() && var->getName() == expected->getName()) {
SILValue value = VarLocs[var].value;
SILType type = value->getType();
Expand Down Expand Up @@ -2754,8 +2768,42 @@ void SILGenFunction::emitSwitchFallthrough(FallthroughStmt *S) {
// Get the destination block.
CaseStmt *caseStmt = S->getFallthroughDest();
JumpDest sharedDest =
context->Emission.getSharedCaseBlockDest(caseStmt);
Cleanups.emitBranchAndCleanups(sharedDest, S);
context->Emission.getSharedCaseBlockDest(caseStmt);

if (!caseStmt->hasBoundDecls()) {
Cleanups.emitBranchAndCleanups(sharedDest, S);
} else {
// Generate branch args to pass along current vars to fallthrough case.
SILModule &M = F.getModule();
ArrayRef<CaseLabelItem> labelItems = caseStmt->getCaseLabelItems();
SmallVector<SILValue, 4> args;
SmallVector<VarDecl *, 4> expectedVarOrder;
labelItems[0].getPattern()->collectVariables(expectedVarOrder);

for (auto *expected : expectedVarOrder) {
if (!expected->hasName())
continue;
for (auto var : VarLocs) {
auto varDecl = dyn_cast<VarDecl>(var.getFirst());
if (varDecl && varDecl->hasName() && varDecl->getName() == expected->getName()) {
SILValue value = var.getSecond().value;

if (value->getType().isAddressOnly(M)) {
context->Emission.emitAddressOnlyInitialization(expected, value);
} else if (var.getSecond().box) {
auto &lowering = getTypeLowering(value->getType());
auto argValue = lowering.emitLoad(B, CurrentSILLoc, value, LoadOwnershipQualifier::Copy);
args.push_back(argValue);
} else {
auto argValue = B.emitCopyValueOperation(CurrentSILLoc, value);
args.push_back(argValue);
}
break;
}
}
}
Cleanups.emitBranchAndCleanups(sharedDest, S, args);
}
}


Expand Down
23 changes: 23 additions & 0 deletions lib/Sema/MiscDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2355,6 +2355,29 @@ class VarDeclUsageChecker : public ASTWalker {
});
}
}

// A fallthrough dest case's bound variable means the source case's
// var of the same name is read.
if (auto *fallthroughStmt = dyn_cast<FallthroughStmt>(S)) {
if (auto *sourceCase = fallthroughStmt->getFallthroughSource()) {
SmallVector<VarDecl *, 4> sourceVars;
auto sourcePattern = sourceCase->getCaseLabelItems()[0].getPattern();
sourcePattern->collectVariables(sourceVars);

auto destCase = fallthroughStmt->getFallthroughDest();
auto destPattern = destCase->getCaseLabelItems()[0].getPattern();
destPattern->forEachVariable([&](VarDecl *V) {
if (!V->hasName())
return;
for (auto *var : sourceVars) {
if (var->hasName() && var->getName() == V->getName()) {
VarDecls[var] |= RK_Read;
break;
}
}
});
}
}

return { true, S };
}
Expand Down
52 changes: 46 additions & 6 deletions lib/Sema/TypeCheckStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,9 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
/// The destination block for a 'fallthrough' statement. Null if the switch
/// scope depth is zero or if we are checking the final 'case' of the current
/// switch.
CaseStmt /*nullable*/ *FallthroughSource = nullptr;
CaseStmt /*nullable*/ *FallthroughDest = nullptr;
FallthroughStmt /*nullable*/ *PreviousFallthrough = nullptr;

SourceLoc EndTypeCheckLoc;

Expand Down Expand Up @@ -818,9 +820,9 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
TC.diagnose(S->getLoc(), diag::fallthrough_from_last_case);
return nullptr;
}
if (FallthroughDest->hasBoundDecls())
TC.diagnose(S->getLoc(), diag::fallthrough_into_case_with_var_binding);
S->setFallthroughSource(FallthroughSource);
S->setFallthroughDest(FallthroughDest);
PreviousFallthrough = S;
return S;
}

Expand All @@ -839,10 +841,12 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
AddLabeledStmt labelNest(*this, S);

auto cases = S->getCases();
CaseStmt *previousBlock = nullptr;
for (auto i = cases.begin(), e = cases.end(); i != e; ++i) {
auto *caseBlock = *i;
// Fallthrough transfers control to the next case block. In the
// final case block, it is invalid.
FallthroughSource = caseBlock;
FallthroughDest = std::next(i) == e ? nullptr : *std::next(i);

for (auto &labelItem : caseBlock->getMutableCaseLabelItems()) {
Expand All @@ -868,12 +872,12 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
// was in the first label item's pattern.
auto firstPattern = caseBlock->getCaseLabelItems()[0].getPattern();
if (pattern != firstPattern) {
SmallVector<VarDecl *, 4> Vars;
firstPattern->collectVariables(Vars);
SmallVector<VarDecl *, 4> vars;
firstPattern->collectVariables(vars);
pattern->forEachVariable([&](VarDecl *VD) {
if (!VD->hasName())
return;
for (auto expected : Vars) {
for (auto *expected : vars) {
if (expected->hasName() && expected->getName() == VD->getName()) {
if (!VD->getType()->isEqual(expected->getType())) {
TC.diagnose(VD->getLoc(), diag::type_mismatch_multiple_pattern_list,
Expand All @@ -887,18 +891,54 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
});
}
}

// Check the guard expression, if present.
if (auto *guard = labelItem.getGuardExpr()) {
hadError |= TC.typeCheckCondition(guard, DC);
labelItem.setGuardExpr(guard);
}
}

// If the previous case fellthrough, similarly check that that case's bindings
// includes our first label item's pattern bindings and types.
if (PreviousFallthrough) {
auto firstPattern = caseBlock->getCaseLabelItems()[0].getPattern();
SmallVector<VarDecl *, 4> Vars;
firstPattern->collectVariables(Vars);

for (auto &labelItem : previousBlock->getCaseLabelItems()) {
const Pattern *pattern = labelItem.getPattern();
SmallVector<VarDecl *, 4> PreviousVars;
pattern->collectVariables(PreviousVars);
for (auto expected : Vars) {
bool matched = false;
if (!expected->hasName())
continue;
for (auto previous: PreviousVars) {
if (previous->hasName() && expected->getName() == previous->getName()) {
if (!previous->getType()->isEqual(expected->getType())) {
TC.diagnose(previous->getLoc(), diag::type_mismatch_fallthrough_pattern_list,
previous->getType(), expected->getType());
previous->markInvalid();
expected->markInvalid();
}
matched = true;
break;
}
}
if (!matched) {
TC.diagnose(PreviousFallthrough->getLoc(),
diag::fallthrough_into_case_with_var_binding, expected->getName());
}
}
}
}

// Type-check the body statements.
PreviousFallthrough = nullptr;
Stmt *body = caseBlock->getBody();
hadError |= typeCheckStmt(body);
caseBlock->setBody(body);
previousBlock = caseBlock;
}

if (!S->isImplicit()) {
Expand Down
2 changes: 1 addition & 1 deletion test/Parse/ConditionalCompilation/switch_case.swift
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ func foo(x: E, intVal: Int) {
// 'fallthrough' target.
switch intVal {
case 1:
fallthrough // expected-error {{'fallthrough' cannot transfer control to a case label that declares variables}}
fallthrough // expected-error {{'fallthrough' from a case which doesn't bind variable 'val'}}
#if ENABLE_C
case let val:
break
Expand Down
21 changes: 19 additions & 2 deletions test/Parse/switch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -251,14 +251,31 @@ func patternVarUsedInAnotherPattern(x: Int) {
}
}

// Fallthroughs can't transfer control into a case label with bindings.
// Fallthroughs can only transfer control into a case label with bindings if the previous case binds a superset of those vars.
switch t {
case (1, 2):
fallthrough // expected-error {{'fallthrough' cannot transfer control to a case label that declares variables}}
fallthrough // expected-error {{'fallthrough' from a case which doesn't bind variable 'a'}} expected-error {{'fallthrough' from a case which doesn't bind variable 'b'}}
case (var a, var b): // expected-warning {{variable 'a' was never mutated; consider changing to 'let' constant}} expected-warning {{variable 'b' was never mutated; consider changing to 'let' constant}}
t = (b, a)
}

switch t { // specifically notice on next line that we shouldn't complain that a is unused - just never mutated
case (var a, let b): // expected-warning {{variable 'a' was never mutated; consider changing to 'let' constant}}
t = (b, b)
fallthrough // ok - notice that subset of bound variables falling through is fine
case (2, let a):
t = (a, a)
}

func patternVarDiffType(x: Int, y: Double) {
switch (x, y) {
case (1, let a): // expected-error {{pattern variable bound to type 'Double', fallthrough case bound to type 'Int'}}
fallthrough
case (let a, _):
break
}
}

func test_label(x : Int) {
Gronk: // expected-error {{switch must be exhaustive}} expected-note{{do you want to add a default clause?}}
switch x {
Expand Down
37 changes: 37 additions & 0 deletions test/SILGen/switch_fallthrough.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ func e() {}
func f() {}
func g() {}

func z(_ i: Int) {}

// CHECK-LABEL: sil hidden @$S18switch_fallthrough5test1yyF
func test1() {
switch foo() {
Expand Down Expand Up @@ -134,3 +136,38 @@ func test4() {
// CHECK-NEXT: tuple ()
// CHECK-NEXT: return
}

// Fallthrough into case block with binding // CHECK-LABEL: sil hidden @$S18switch_fallthrough5test5yyF
func test5() {
switch (foo(), bar()) {
// CHECK: cond_br {{%.*}}, [[YES_CASE1:bb[0-9]+]], {{bb[0-9]+}}
// CHECK: [[YES_CASE1]]:
case (var n, foo()):
// Check that the var is boxed and unboxed and the final value is the one that falls through into the next case
// CHECK: [[BOX:%.*]] = alloc_box ${ var Int }, var, name "n"
// CHECK: [[N_BOX:%.*]] = project_box [[BOX]] : ${ var Int }, 0
// CHECK: function_ref @$S18switch_fallthrough1ayyF
// CHECK: [[N:%.*]] = load [trivial] [[N_BOX]] : $*Int
// CHECK: destroy_value [[BOX]] : ${ var Int }
// CHECK: br [[CASE2:bb[0-9]+]]([[N]] : $Int)
a()
fallthrough
case (foo(), let n):
// CHECK: cond_br {{%.*}}, [[YES_SECOND_CONDITION:bb[0-9]+]], {{bb[0-9]+}}
// CHECK: [[YES_SECOND_CONDITION]]:
// CHECK: debug_value [[SECOND_N:%.*]] : $Int, let, name "n"
// CHECK: br [[CASE2]]([[SECOND_N]] : $Int)

// CHECK: [[CASE2]]([[INCOMING_N:%.*]] : @trivial $Int):
// CHECK: [[Z:%.*]] = function_ref @$S18switch_fallthrough1zyySiF
// CHECK apply [[Z]]([[INCOMING_N]]) : $@convention(thin) (Int) -> ()
// CHECK: br [[CONT:bb[0-9]+]]
z(n)
case (_, _):
break
}
// CHECK: [[CONT]]:
// CHECK: function_ref @$S18switch_fallthrough1eyyF
e()
}