Support for fallthrough into cases with pattern variables.

This commit is contained in:
gregomni
2018-01-19 12:33:08 -08:00
parent 36496ae9f7
commit 0c3c0fd59b
9 changed files with 266 additions and 21 deletions

View File

@@ -2889,14 +2889,16 @@ ERROR(fallthrough_outside_switch,none,
ERROR(fallthrough_from_last_case,none, ERROR(fallthrough_from_last_case,none,
"'fallthrough' without a following 'case' or 'default' block", ()) "'fallthrough' without a following 'case' or 'default' block", ())
ERROR(fallthrough_into_case_with_var_binding,none, 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, ERROR(unnecessary_cast_over_optionset,none,
"unnecessary cast over raw value of %0", (Type)) "unnecessary cast over raw value of %0", (Type))
ERROR(type_mismatch_multiple_pattern_list,none, ERROR(type_mismatch_multiple_pattern_list,none,
"pattern variable bound to type %0, expected type %1", (Type, Type)) "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, WARNING(where_on_one_item, none,
"'where' only applies to the second pattern match in this case", ()) "'where' only applies to the second pattern match in this case", ())

View File

@@ -1052,18 +1052,29 @@ public:
/// FallthroughStmt - The keyword "fallthrough". /// FallthroughStmt - The keyword "fallthrough".
class FallthroughStmt : public Stmt { class FallthroughStmt : public Stmt {
SourceLoc Loc; SourceLoc Loc;
CaseStmt *FallthroughSource;
CaseStmt *FallthroughDest; CaseStmt *FallthroughDest;
public: public:
FallthroughStmt(SourceLoc Loc, Optional<bool> implicit = None) FallthroughStmt(SourceLoc Loc, Optional<bool> implicit = None)
: Stmt(StmtKind::Fallthrough, getDefaultImplicitFlag(implicit, Loc)), : Stmt(StmtKind::Fallthrough, getDefaultImplicitFlag(implicit, Loc)),
Loc(Loc), FallthroughDest(nullptr) Loc(Loc), FallthroughSource(nullptr), FallthroughDest(nullptr)
{} {}
SourceLoc getLoc() const { return Loc; } SourceLoc getLoc() const { return Loc; }
SourceRange getSourceRange() 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. /// Get the CaseStmt block to which the fallthrough transfers control.
/// Set during Sema. /// Set during Sema.
CaseStmt *getFallthroughDest() const { CaseStmt *getFallthroughDest() const {

View File

@@ -1075,7 +1075,8 @@ void PatternMatchEmission::emitWildcardDispatch(ClauseMatrix &clauses,
bool hasMultipleItems = false; bool hasMultipleItems = false;
if (auto *caseStmt = dyn_cast<CaseStmt>(stmt)) { 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. // Bind the rest of the patterns.
@@ -2383,7 +2384,20 @@ void PatternMatchEmission::emitAddressOnlyAllocations() {
return; return;
SILType ty = SGF.getLoweredType(V->getType()); 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())) { if (ty.isAddressOnly(SGF.F.getModule())) {
assert(!Temporaries[V]); assert(!Temporaries[V]);
Temporaries[V] = SGF.emitTemporaryAllocation(V, ty); Temporaries[V] = SGF.emitTemporaryAllocation(V, ty);
@@ -2617,7 +2631,7 @@ void SILGenFunction::emitSwitchStmt(SwitchStmt *S) {
// statement. // statement.
JumpDest sharedDest = emission.getSharedCaseBlockDest(caseBlock); JumpDest sharedDest = emission.getSharedCaseBlockDest(caseBlock);
Cleanups.emitBranchAndCleanups(sharedDest, caseBlock); Cleanups.emitBranchAndCleanups(sharedDest, caseBlock);
} else if (caseBlock->getCaseLabelItems().size() > 1) { } else if (row.hasFallthroughTo() || caseBlock->getCaseLabelItems().size() > 1) {
JumpDest sharedDest = JumpDest sharedDest =
emission.getSharedCaseBlockDest(caseBlock); emission.getSharedCaseBlockDest(caseBlock);
@@ -2630,15 +2644,15 @@ void SILGenFunction::emitSwitchStmt(SwitchStmt *S) {
ArrayRef<CaseLabelItem> labelItems = caseBlock->getCaseLabelItems(); ArrayRef<CaseLabelItem> labelItems = caseBlock->getCaseLabelItems();
SmallVector<SILValue, 4> args; SmallVector<SILValue, 4> args;
SmallVector<VarDecl *, 4> expectedVarOrder; SmallVector<VarDecl *, 4> expectedVarOrder;
SmallVector<VarDecl *, 4> Vars; SmallVector<VarDecl *, 4> vars;
labelItems[0].getPattern()->collectVariables(expectedVarOrder); labelItems[0].getPattern()->collectVariables(expectedVarOrder);
row.getCasePattern()->collectVariables(Vars); row.getCasePattern()->collectVariables(vars);
SILModule &M = F.getModule(); SILModule &M = F.getModule();
for (auto expected : expectedVarOrder) { for (auto expected : expectedVarOrder) {
if (!expected->hasName()) if (!expected->hasName())
continue; continue;
for (auto var : Vars) { for (auto *var : vars) {
if (var->hasName() && var->getName() == expected->getName()) { if (var->hasName() && var->getName() == expected->getName()) {
SILValue value = VarLocs[var].value; SILValue value = VarLocs[var].value;
SILType type = value->getType(); SILType type = value->getType();
@@ -2754,8 +2768,42 @@ void SILGenFunction::emitSwitchFallthrough(FallthroughStmt *S) {
// Get the destination block. // Get the destination block.
CaseStmt *caseStmt = S->getFallthroughDest(); CaseStmt *caseStmt = S->getFallthroughDest();
JumpDest sharedDest = JumpDest sharedDest =
context->Emission.getSharedCaseBlockDest(caseStmt); context->Emission.getSharedCaseBlockDest(caseStmt);
Cleanups.emitBranchAndCleanups(sharedDest, S);
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);
}
} }

View File

@@ -2355,6 +2355,29 @@ public:
}); });
} }
} }
// 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 }; return { true, S };
} }

View File

@@ -303,7 +303,9 @@ public:
/// The destination block for a 'fallthrough' statement. Null if the switch /// 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 /// scope depth is zero or if we are checking the final 'case' of the current
/// switch. /// switch.
CaseStmt /*nullable*/ *FallthroughSource = nullptr;
CaseStmt /*nullable*/ *FallthroughDest = nullptr; CaseStmt /*nullable*/ *FallthroughDest = nullptr;
FallthroughStmt /*nullable*/ *PreviousFallthrough = nullptr;
SourceLoc EndTypeCheckLoc; SourceLoc EndTypeCheckLoc;
@@ -818,9 +820,9 @@ public:
TC.diagnose(S->getLoc(), diag::fallthrough_from_last_case); TC.diagnose(S->getLoc(), diag::fallthrough_from_last_case);
return nullptr; return nullptr;
} }
if (FallthroughDest->hasBoundDecls()) S->setFallthroughSource(FallthroughSource);
TC.diagnose(S->getLoc(), diag::fallthrough_into_case_with_var_binding);
S->setFallthroughDest(FallthroughDest); S->setFallthroughDest(FallthroughDest);
PreviousFallthrough = S;
return S; return S;
} }
@@ -839,10 +841,12 @@ public:
AddLabeledStmt labelNest(*this, S); AddLabeledStmt labelNest(*this, S);
auto cases = S->getCases(); auto cases = S->getCases();
CaseStmt *previousBlock = nullptr;
for (auto i = cases.begin(), e = cases.end(); i != e; ++i) { for (auto i = cases.begin(), e = cases.end(); i != e; ++i) {
auto *caseBlock = *i; auto *caseBlock = *i;
// Fallthrough transfers control to the next case block. In the // Fallthrough transfers control to the next case block. In the
// final case block, it is invalid. // final case block, it is invalid.
FallthroughSource = caseBlock;
FallthroughDest = std::next(i) == e ? nullptr : *std::next(i); FallthroughDest = std::next(i) == e ? nullptr : *std::next(i);
for (auto &labelItem : caseBlock->getMutableCaseLabelItems()) { for (auto &labelItem : caseBlock->getMutableCaseLabelItems()) {
@@ -868,12 +872,12 @@ public:
// was in the first label item's pattern. // was in the first label item's pattern.
auto firstPattern = caseBlock->getCaseLabelItems()[0].getPattern(); auto firstPattern = caseBlock->getCaseLabelItems()[0].getPattern();
if (pattern != firstPattern) { if (pattern != firstPattern) {
SmallVector<VarDecl *, 4> Vars; SmallVector<VarDecl *, 4> vars;
firstPattern->collectVariables(Vars); firstPattern->collectVariables(vars);
pattern->forEachVariable([&](VarDecl *VD) { pattern->forEachVariable([&](VarDecl *VD) {
if (!VD->hasName()) if (!VD->hasName())
return; return;
for (auto expected : Vars) { for (auto *expected : vars) {
if (expected->hasName() && expected->getName() == VD->getName()) { if (expected->hasName() && expected->getName() == VD->getName()) {
if (!VD->getType()->isEqual(expected->getType())) { if (!VD->getType()->isEqual(expected->getType())) {
TC.diagnose(VD->getLoc(), diag::type_mismatch_multiple_pattern_list, TC.diagnose(VD->getLoc(), diag::type_mismatch_multiple_pattern_list,
@@ -887,18 +891,54 @@ public:
}); });
} }
} }
// Check the guard expression, if present. // Check the guard expression, if present.
if (auto *guard = labelItem.getGuardExpr()) { if (auto *guard = labelItem.getGuardExpr()) {
hadError |= TC.typeCheckCondition(guard, DC); hadError |= TC.typeCheckCondition(guard, DC);
labelItem.setGuardExpr(guard); 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. // Type-check the body statements.
PreviousFallthrough = nullptr;
Stmt *body = caseBlock->getBody(); Stmt *body = caseBlock->getBody();
hadError |= typeCheckStmt(body); hadError |= typeCheckStmt(body);
caseBlock->setBody(body); caseBlock->setBody(body);
previousBlock = caseBlock;
} }
if (!S->isImplicit()) { if (!S->isImplicit()) {

View File

@@ -206,7 +206,7 @@ func foo(x: E, intVal: Int) {
// 'fallthrough' target. // 'fallthrough' target.
switch intVal { switch intVal {
case 1: 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 #if ENABLE_C
case let val: case let val:
break break

View File

@@ -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 { switch t {
case (1, 2): 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}} 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) 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) { func test_label(x : Int) {
Gronk: // expected-error {{switch must be exhaustive}} expected-note{{do you want to add a default clause?}} Gronk: // expected-error {{switch must be exhaustive}} expected-note{{do you want to add a default clause?}}
switch x { switch x {

View File

@@ -16,6 +16,8 @@ func e() {}
func f() {} func f() {}
func g() {} func g() {}
func z(_ i: Int) {}
// CHECK-LABEL: sil hidden @$S18switch_fallthrough5test1yyF // CHECK-LABEL: sil hidden @$S18switch_fallthrough5test1yyF
func test1() { func test1() {
switch foo() { switch foo() {
@@ -134,3 +136,38 @@ func test4() {
// CHECK-NEXT: tuple () // CHECK-NEXT: tuple ()
// CHECK-NEXT: return // 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()
}

View File

@@ -131,3 +131,70 @@ func multipleLabelsVar(e: E) {
break break
} }
} }
// CHECK-LABEL: sil hidden @$S34switch_multiple_entry_address_only20fallthroughWithValue1eyAA1EO_tF : $@convention(thin) (@in E) -> ()
func fallthroughWithValue(e: E) {
// CHECK: bb0
// CHECK: [[X_PHI:%.*]] = alloc_stack $Any
// CHECK-NEXT: [[E_COPY:%.*]] = alloc_stack $E
// CHECK-NEXT: copy_addr %0 to [initialization] [[E_COPY]]
// CHECK-NEXT: switch_enum_addr [[E_COPY]] : $*E, case #E.a!enumelt.1: bb1, case #E.b!enumelt.1: bb2, default bb4
// CHECK: bb1:
// CHECK-NEXT: [[E_PAYLOAD:%.*]] = unchecked_take_enum_data_addr [[E_COPY]] : $*E, #E.a!enumelt.1
// CHECK-NEXT: [[ORIGINAL_ANY_BOX:%.*]] = alloc_stack $Any
// CHECK-NEXT: copy_addr [take] [[E_PAYLOAD]] to [initialization] [[ORIGINAL_ANY_BOX]]
// CHECK-NEXT: [[ANY_BOX:%.*]] = alloc_stack $Any
// CHECK-NEXT: copy_addr [[ORIGINAL_ANY_BOX]] to [initialization] [[ANY_BOX]]
// CHECK: [[FN1:%.*]] = function_ref @$S34switch_multiple_entry_address_only8takesAnyyyypF
// CHECK-NEXT: apply [[FN1]]([[ANY_BOX]]
// CHECK-NEXT: dealloc_stack [[ANY_BOX]]
// CHECK-NEXT: copy_addr [[ORIGINAL_ANY_BOX]] to [initialization] [[X_PHI]]
// CHECK-NEXT: destroy_addr [[ORIGINAL_ANY_BOX]]
// CHECK-NEXT: dealloc_stack [[ORIGINAL_ANY_BOX]]
// CHECK-NEXT: dealloc_stack [[E_COPY]]
// CHECK-NEXT: br bb3
// CHECK: bb2:
// CHECK-NEXT: [[E_PAYLOAD:%.*]] = unchecked_take_enum_data_addr [[E_COPY]] : $*E, #E.b!enumelt.1
// CHECK-NEXT: [[ANY_BOX:%.*]] = alloc_stack $Any
// CHECK-NEXT: copy_addr [take] [[E_PAYLOAD]] to [initialization] [[ANY_BOX]]
// CHECK-NEXT: copy_addr [[ANY_BOX]] to [initialization] [[X_PHI]]
// CHECK-NEXT: destroy_addr [[ANY_BOX]]
// CHECK-NEXT: dealloc_stack [[ANY_BOX]]
// CHECK-NEXT: dealloc_stack [[E_COPY]]
// CHECK-NEXT: br bb3
// CHECK: bb3:
// CHECK-NEXT: [[ANY_BOX:%.*]] = alloc_stack $Any
// CHECK-NEXT: copy_addr [[X_PHI]] to [initialization] [[ANY_BOX]]
// CHECK: [[FN2:%.*]] = function_ref @$S34switch_multiple_entry_address_only8takesAnyyyypF
// CHECK-NEXT: apply [[FN2]]([[ANY_BOX]]
// CHECK-NEXT: dealloc_stack [[ANY_BOX]]
// CHECK-NEXT: destroy_addr [[X_PHI]]
// CHECK-NEXT: br bb6
// CHECK: bb4:
// CHECK-NEXT: br bb5
// CHECK: bb5:
// CHECK-NEXT: destroy_addr [[E_COPY]]
// CHECK-NEXT: dealloc_stack [[E_COPY]]
// CHECK-NEXT: br bb6
// CHECK: bb6:
// CHECK-NEXT: dealloc_stack [[X_PHI]]
// CHECK-NEXT: destroy_addr %0
// CHECK-NEXT: tuple ()
// CHECK-NEXT: return
switch e {
case .a(let x):
takesAny(x)
fallthrough
case .b(let x):
takesAny(x)
default:
break
}
}