Merge pull request #75891 from hamishknight/fellthrough

[Sema] Improve handling of `fallthrough` in `if`/`switch` expressions
This commit is contained in:
Hamish Knight
2024-08-19 17:59:24 +01:00
committed by GitHub
22 changed files with 423 additions and 128 deletions

View File

@@ -1491,10 +1491,10 @@ BridgedDoCatchStmt BridgedDoCatchStmt_createParsed(
BridgedNullableTypeRepr cThrownType, BridgedStmt cBody,
BridgedArrayRef cCatches);
SWIFT_NAME("BridgedFallthroughStmt.createParsed(_:loc:)")
SWIFT_NAME("BridgedFallthroughStmt.createParsed(loc:declContext:)")
BridgedFallthroughStmt
BridgedFallthroughStmt_createParsed(BridgedASTContext cContext,
BridgedSourceLoc cLoc);
BridgedFallthroughStmt_createParsed(BridgedSourceLoc cLoc,
BridgedDeclContext cDC);
SWIFT_NAME("BridgedForEachStmt.createParsed(_:labelInfo:forLoc:tryLoc:awaitLoc:"
"pattern:inLoc:sequence:whereLoc:whereExpr:body:)")

View File

@@ -1329,10 +1329,11 @@ ERROR(single_value_stmt_branch_empty,none,
"expected expression in branch of '%0' expression",
(StmtKind))
ERROR(single_value_stmt_branch_must_end_in_result,none,
"non-expression branch of '%0' expression may only end with a 'throw'",
(StmtKind))
"non-expression branch of '%0' expression may only end with a 'throw'"
"%select{| or 'fallthrough'}1",
(StmtKind, bool))
ERROR(cannot_jump_in_single_value_stmt,none,
"cannot '%0' in '%1' when used as expression",
"cannot use '%0' to transfer control out of '%1' expression",
(StmtKind, StmtKind))
WARNING(effect_marker_on_single_value_stmt,none,
"'%0' has no effect on '%1' expression", (StringRef, StmtKind))

View File

@@ -1159,36 +1159,29 @@ public:
/// FallthroughStmt - The keyword "fallthrough".
class FallthroughStmt : public Stmt {
SourceLoc Loc;
CaseStmt *FallthroughSource;
CaseStmt *FallthroughDest;
DeclContext *DC;
public:
FallthroughStmt(SourceLoc Loc, std::optional<bool> implicit = std::nullopt)
FallthroughStmt(SourceLoc Loc, DeclContext *DC,
std::optional<bool> implicit = std::nullopt)
: Stmt(StmtKind::Fallthrough, getDefaultImplicitFlag(implicit, Loc)),
Loc(Loc), FallthroughSource(nullptr), FallthroughDest(nullptr) {}
Loc(Loc), DC(DC) {}
public:
static FallthroughStmt *createParsed(SourceLoc Loc, DeclContext *DC);
SourceLoc getLoc() const { return Loc; }
SourceRange getSourceRange() const { return Loc; }
DeclContext *getDeclContext() const { return DC; }
void setDeclContext(DeclContext *newDC) { DC = newDC; }
/// 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;
}
/// Returns \c nullptr if the fallthrough is invalid.
CaseStmt *getFallthroughSource() const;
/// Get the CaseStmt block to which the fallthrough transfers control.
/// Set during Sema.
CaseStmt *getFallthroughDest() const {
assert(FallthroughDest && "fallthrough dest is not set until Sema");
return FallthroughDest;
}
void setFallthroughDest(CaseStmt *C) {
assert(!FallthroughDest && "fallthrough dest already set?!");
FallthroughDest = C;
}
/// Returns \c nullptr if the fallthrough is invalid.
CaseStmt *getFallthroughDest() const;
static bool classof(const Stmt *S) {
return S->getKind() == StmtKind::Fallthrough;
@@ -1613,6 +1606,7 @@ public:
}
DeclContext *getDeclContext() const { return DC; }
void setDeclContext(DeclContext *newDC) { DC = newDC; }
static bool classof(const Stmt *S) {
return S->getKind() == StmtKind::Break;
@@ -1648,6 +1642,7 @@ public:
}
DeclContext *getDeclContext() const { return DC; }
void setDeclContext(DeclContext *newDC) { DC = newDC; }
static bool classof(const Stmt *S) {
return S->getKind() == StmtKind::Continue;

View File

@@ -4162,6 +4162,29 @@ public:
bool isCached() const { return true; }
};
struct FallthroughSourceAndDest {
CaseStmt *Source;
CaseStmt *Dest;
};
/// Lookup the source and destination of a 'fallthrough'.
class FallthroughSourceAndDestRequest
: public SimpleRequest<FallthroughSourceAndDestRequest,
FallthroughSourceAndDest(const FallthroughStmt *),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;
private:
friend SimpleRequest;
FallthroughSourceAndDest evaluate(Evaluator &evaluator,
const FallthroughStmt *FS) const;
public:
bool isCached() const { return true; }
};
/// Precheck a ReturnStmt, which involves some initial validation, as well as
/// applying a conversion to a FailStmt if needed.
class PreCheckReturnStmtRequest

View File

@@ -482,6 +482,9 @@ SWIFT_REQUEST(TypeChecker, BreakTargetRequest,
SWIFT_REQUEST(TypeChecker, ContinueTargetRequest,
LabeledStmt *(const ContinueStmt *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, FallthroughSourceAndDestRequest,
FallthroughSourceAndDest(const FallthroughStmt *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, PreCheckReturnStmtRequest,
Stmt *(ReturnStmt *, DeclContext *),
Cached, NoLocationInfo)

View File

@@ -2005,9 +2005,9 @@ BridgedDoCatchStmt BridgedDoCatchStmt_createParsed(
}
BridgedFallthroughStmt
BridgedFallthroughStmt_createParsed(BridgedASTContext cContext,
BridgedSourceLoc cLoc) {
return new (cContext.unbridged()) FallthroughStmt(cLoc.unbridged());
BridgedFallthroughStmt_createParsed(BridgedSourceLoc cLoc,
BridgedDeclContext cDC) {
return FallthroughStmt::createParsed(cLoc.unbridged(), cDC.unbridged());
}
BridgedForEachStmt BridgedForEachStmt_createParsed(

View File

@@ -989,6 +989,23 @@ LabeledStmt *ContinueStmt::getTarget() const {
return evaluateOrDefault(eval, ContinueTargetRequest{this}, nullptr);
}
FallthroughStmt *FallthroughStmt::createParsed(SourceLoc Loc, DeclContext *DC) {
auto &ctx = DC->getASTContext();
return new (ctx) FallthroughStmt(Loc, DC);
}
CaseStmt *FallthroughStmt::getFallthroughSource() const {
auto &eval = getDeclContext()->getASTContext().evaluator;
return evaluateOrDefault(eval, FallthroughSourceAndDestRequest{this}, {})
.Source;
}
CaseStmt *FallthroughStmt::getFallthroughDest() const {
auto &eval = getDeclContext()->getASTContext().evaluator;
return evaluateOrDefault(eval, FallthroughSourceAndDestRequest{this}, {})
.Dest;
}
SourceLoc swift::extractNearestSourceLoc(const Stmt *S) {
return S->getStartLoc();
}

View File

@@ -339,8 +339,8 @@ extension ASTGenVisitor {
func generate(fallThroughStmt node: FallThroughStmtSyntax) -> BridgedFallthroughStmt {
return .createParsed(
self.ctx,
loc: self.generateSourceLoc(node.fallthroughKeyword)
loc: self.generateSourceLoc(node.fallthroughKeyword),
declContext: self.declContext
)
}

View File

@@ -648,8 +648,8 @@ ParserResult<Stmt> Parser::parseStmt(bool fromASTGen) {
if (LabelInfo) diagnose(LabelInfo.Loc, diag::invalid_label_on_stmt);
if (tryLoc.isValid()) diagnose(tryLoc, diag::try_on_stmt, Tok.getText());
return makeParserResult(
new (Context) FallthroughStmt(consumeToken(tok::kw_fallthrough)));
auto loc = consumeToken(tok::kw_fallthrough);
return makeParserResult(FallthroughStmt::createParsed(loc, CurDeclContext));
}
case tok::pound_assert:
if (LabelInfo) diagnose(LabelInfo.Loc, diag::invalid_label_on_stmt);

View File

@@ -1794,7 +1794,7 @@ private:
}
ASTNode visitFallthroughStmt(FallthroughStmt *fallthroughStmt) {
if (checkFallthroughStmt(context.getAsDeclContext(), fallthroughStmt))
if (checkFallthroughStmt(fallthroughStmt))
hadError = true;
return fallthroughStmt;
}

View File

@@ -4478,7 +4478,7 @@ private:
// default.
Diags.diagnose(branch->getEndLoc(),
diag::single_value_stmt_branch_must_end_in_result,
S->getKind());
S->getKind(), isa<SwitchStmt>(S));
}
break;
}

View File

@@ -742,6 +742,25 @@ ContinueTargetRequest::evaluate(Evaluator &evaluator,
CS->getTargetName(), CS->getTargetLoc(), /*isContinue*/ true, DC);
}
FallthroughSourceAndDest
FallthroughSourceAndDestRequest::evaluate(Evaluator &evaluator,
const FallthroughStmt *FS) const {
auto *SF = FS->getDeclContext()->getParentSourceFile();
auto &ctx = SF->getASTContext();
auto loc = FS->getLoc();
auto [src, dest] = ASTScope::lookupFallthroughSourceAndDest(SF, loc);
if (!src) {
ctx.Diags.diagnose(loc, diag::fallthrough_outside_switch);
return {};
}
if (!dest) {
ctx.Diags.diagnose(loc, diag::fallthrough_from_last_case);
return {};
}
return {src, dest};
}
static Expr *getDeclRefProvidingExpressionForHasSymbol(Expr *E) {
// Strip coercions, which are necessary in source to disambiguate overloaded
// functions or generic functions, e.g.
@@ -925,12 +944,18 @@ static bool typeCheckConditionForStatement(LabeledConditionalStmt *stmt,
return false;
}
/// Verify that the pattern bindings for the cases that we're falling through
/// from and to are equivalent.
static void checkFallthroughPatternBindingsAndTypes(
ASTContext &ctx,
CaseStmt *caseBlock, CaseStmt *previousBlock,
FallthroughStmt *fallthrough) {
/// Check the correctness of a 'fallthrough' statement.
///
/// \returns true if an error occurred.
bool swift::checkFallthroughStmt(FallthroughStmt *FS) {
auto &ctx = FS->getDeclContext()->getASTContext();
auto *caseBlock = FS->getFallthroughDest();
auto *previousBlock = FS->getFallthroughSource();
if (!previousBlock || !caseBlock)
return true;
// Verify that the pattern bindings for the cases that we're falling through
// from and to are equivalent.
auto firstPattern = caseBlock->getCaseLabelItems()[0].getPattern();
SmallVector<VarDecl *, 4> vars;
firstPattern->collectVariables(vars);
@@ -969,36 +994,10 @@ static void checkFallthroughPatternBindingsAndTypes(
if (!matched) {
ctx.Diags.diagnose(
fallthrough->getLoc(), diag::fallthrough_into_case_with_var_binding,
FS->getLoc(), diag::fallthrough_into_case_with_var_binding,
expected->getName());
}
}
}
/// Check the correctness of a 'fallthrough' statement.
///
/// \returns true if an error occurred.
bool swift::checkFallthroughStmt(DeclContext *dc, FallthroughStmt *stmt) {
CaseStmt *fallthroughSource;
CaseStmt *fallthroughDest;
ASTContext &ctx = dc->getASTContext();
auto sourceFile = dc->getParentSourceFile();
std::tie(fallthroughSource, fallthroughDest) =
ASTScope::lookupFallthroughSourceAndDest(sourceFile, stmt->getLoc());
if (!fallthroughSource) {
ctx.Diags.diagnose(stmt->getLoc(), diag::fallthrough_outside_switch);
return true;
}
if (!fallthroughDest) {
ctx.Diags.diagnose(stmt->getLoc(), diag::fallthrough_from_last_case);
return true;
}
stmt->setFallthroughSource(fallthroughSource);
stmt->setFallthroughDest(fallthroughDest);
checkFallthroughPatternBindingsAndTypes(
ctx, fallthroughDest, fallthroughSource, stmt);
return false;
}
@@ -1457,7 +1456,7 @@ public:
}
Stmt *visitFallthroughStmt(FallthroughStmt *S) {
if (checkFallthroughStmt(DC, S))
if (checkFallthroughStmt(S))
return nullptr;
return S;
@@ -3023,11 +3022,14 @@ namespace {
/// An ASTWalker that searches for any break/continue/return statements that
/// jump out of the context the walker starts at.
class JumpOutOfContextFinder : public ASTWalker {
const Stmt *ParentStmt;
TinyPtrVector<Stmt *> &Jumps;
SmallPtrSet<Stmt *, 4> ParentLabeledStmts;
SmallPtrSet<CaseStmt *, 4> ParentCaseStmts;
public:
JumpOutOfContextFinder(TinyPtrVector<Stmt *> &jumps) : Jumps(jumps) {}
JumpOutOfContextFinder(const Stmt *parentStmt, TinyPtrVector<Stmt *> &jumps)
: ParentStmt(parentStmt), Jumps(jumps) {}
MacroWalking getMacroWalkingBehavior() const override {
return MacroWalking::Expansion;
@@ -3036,9 +3038,11 @@ public:
PreWalkResult<Stmt *> walkToStmtPre(Stmt *S) override {
if (auto *LS = dyn_cast<LabeledStmt>(S))
ParentLabeledStmts.insert(LS);
if (auto *CS = dyn_cast<CaseStmt>(S))
ParentCaseStmts.insert(CS);
// Cannot 'break', 'continue', or 'return' out of the statement. A jump to
// a statement within a branch however is fine.
// Cannot 'break', 'continue', 'fallthrough' or 'return' out of the
// statement. A jump to a statement within a branch however is fine.
if (auto *BS = dyn_cast<BreakStmt>(S)) {
if (!ParentLabeledStmts.contains(BS->getTarget()))
Jumps.push_back(BS);
@@ -3047,6 +3051,17 @@ public:
if (!ParentLabeledStmts.contains(CS->getTarget()))
Jumps.push_back(CS);
}
if (auto *FS = dyn_cast<FallthroughStmt>(S)) {
// The source must either be in the parent statement, or must be a
// nested CaseStmt we've seen. If there's no source, we will have
// already diagnosed.
if (auto *source = FS->getFallthroughSource()) {
if (source->getParentStmt() != ParentStmt &&
!ParentCaseStmts.contains(source)) {
Jumps.push_back(FS);
}
}
}
if (isa<ReturnStmt>(S) || isa<FailStmt>(S))
Jumps.push_back(S);
@@ -3058,6 +3073,11 @@ public:
assert(removed);
(void)removed;
}
if (auto *CS = dyn_cast<CaseStmt>(S)) {
auto removed = ParentCaseStmts.erase(CS);
assert(removed);
(void)removed;
}
return Action::Continue(S);
}
@@ -3072,23 +3092,12 @@ public:
};
} // end anonymous namespace
/// Whether the given brace statement ends with a 'throw'.
static bool doesBraceEndWithThrow(BraceStmt *BS) {
if (BS->empty())
return false;
auto *S = BS->getLastElement().dyn_cast<Stmt *>();
if (!S)
return false;
return isa<ThrowStmt>(S);
}
IsSingleValueStmtResult
areBranchesValidForSingleValueStmt(ASTContext &ctx, ArrayRef<Stmt *> branches) {
areBranchesValidForSingleValueStmt(ASTContext &ctx, const Stmt *parentStmt,
ArrayRef<Stmt *> branches) {
TinyPtrVector<Stmt *> invalidJumps;
TinyPtrVector<Stmt *> unterminatedBranches;
JumpOutOfContextFinder jumpFinder(invalidJumps);
JumpOutOfContextFinder jumpFinder(parentStmt, invalidJumps);
// Must have a single expression brace, and non-single-expression branches
// must end with a throw.
@@ -3109,8 +3118,20 @@ areBranchesValidForSingleValueStmt(ASTContext &ctx, ArrayRef<Stmt *> branches) {
continue;
}
// If there was no result, the branch must end in a 'throw'.
if (!doesBraceEndWithThrow(BS))
// If there was no result, the branch must end in a 'throw' or
// 'fallthrough'.
auto endsInJump = [&]() {
if (BS->empty())
return false;
auto *S = BS->getLastElement().dyn_cast<Stmt *>();
if (!S)
return false;
return isa<ThrowStmt>(S) || isa<FallthroughStmt>(S);
}();
if (!endsInJump)
unterminatedBranches.push_back(BS);
}
@@ -3146,17 +3167,19 @@ IsSingleValueStmtRequest::evaluate(Evaluator &eval, const Stmt *S,
return IsSingleValueStmtResult::nonExhaustiveIf();
SmallVector<Stmt *, 4> scratch;
return areBranchesValidForSingleValueStmt(ctx, IS->getBranches(scratch));
return areBranchesValidForSingleValueStmt(ctx, IS,
IS->getBranches(scratch));
}
if (auto *SS = dyn_cast<SwitchStmt>(S)) {
SmallVector<Stmt *, 4> scratch;
return areBranchesValidForSingleValueStmt(ctx, SS->getBranches(scratch));
return areBranchesValidForSingleValueStmt(ctx, SS,
SS->getBranches(scratch));
}
if (auto *DS = dyn_cast<DoStmt>(S)) {
if (!ctx.LangOpts.hasFeature(Feature::DoExpressions))
return IsSingleValueStmtResult::unhandledStmt();
return areBranchesValidForSingleValueStmt(ctx, DS->getBody());
return areBranchesValidForSingleValueStmt(ctx, DS, DS->getBody());
}
if (auto *DCS = dyn_cast<DoCatchStmt>(S)) {
if (!ctx.LangOpts.hasFeature(Feature::DoExpressions))
@@ -3166,7 +3189,8 @@ IsSingleValueStmtRequest::evaluate(Evaluator &eval, const Stmt *S,
return IsSingleValueStmtResult::nonExhaustiveDoCatch();
SmallVector<Stmt *, 4> scratch;
return areBranchesValidForSingleValueStmt(ctx, DCS->getBranches(scratch));
return areBranchesValidForSingleValueStmt(ctx, DCS,
DCS->getBranches(scratch));
}
return IsSingleValueStmtResult::unhandledStmt();
}

View File

@@ -1588,6 +1588,14 @@ namespace {
for (auto *CaseVar : CS->getCaseBodyVariablesOrEmptyArray())
CaseVar->setDeclContext(NewDC);
}
// A few statements store DeclContexts, update them.
if (auto *BS = dyn_cast<BreakStmt>(S))
BS->setDeclContext(NewDC);
if (auto *CS = dyn_cast<ContinueStmt>(S))
CS->setDeclContext(NewDC);
if (auto *FS = dyn_cast<FallthroughStmt>(S))
FS->setDeclContext(NewDC);
return Action::Continue(S);
}

View File

@@ -1433,7 +1433,7 @@ LabeledStmt *findBreakOrContinueStmtTarget(ASTContext &ctx,
/// Check the correctness of a 'fallthrough' statement.
///
/// \returns true if an error occurred.
bool checkFallthroughStmt(DeclContext *dc, FallthroughStmt *stmt);
bool checkFallthroughStmt(FallthroughStmt *stmt);
/// Check for restrictions on the use of the @unknown attribute on a
/// case statement.

View File

@@ -15,7 +15,7 @@ func testLocalFn() {
func testLocalBinding() {
bar() {
let _ = if .random() { return () } else { 0 }
// expected-error@-1 {{cannot 'return' in 'if' when used as expression}}
// expected-error@-1 {{cannot use 'return' to transfer control out of 'if' expression}}
return ()
}
}

View File

@@ -8,7 +8,7 @@ func test() {
foo {
bar(if true { return } else { return })
// expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
// expected-error@-2 2{{cannot 'return' in 'if' when used as expression}}
// expected-error@-2 2{{cannot use 'return' to transfer control out of 'if' expression}}
}
foo {
bar(if true { { return } } else { { return } })

View File

@@ -659,3 +659,36 @@ struct LazyProp {
0
}
}
func testNestedFallthrough1() throws -> Int {
let x = if .random() {
switch Bool.random() {
case true:
fallthrough
case false:
break
}
throw Err()
} else {
0
}
return x
}
func testNestedFallthrough2() throws -> Int {
let x = if .random() {
switch Bool.random() {
case true:
if .random() {
fallthrough
}
break
case false:
break
}
throw Err()
} else {
0
}
return x
}

View File

@@ -168,6 +168,52 @@ func testFallthrough() throws -> Int {
// CHECK: dealloc_stack [[RESULT]] : $*Int
// CHECK: return [[VAL]] : $Int
func testFallthrough2() -> Int {
let x = switch Bool.random() {
case true:
fallthrough
case false:
1
}
return x
}
// CHECK-LABEL: sil hidden [ossa] @$s11switch_expr16testFallthrough2SiyF : $@convention(thin) () -> Int {
// CHECK: [[RESULT:%[0-9]+]] = alloc_stack $Int
// CHECK: switch_value {{%[0-9]+}} : $Builtin.Int1, case {{%[0-9]+}}: [[TRUEBB:bb[0-9]+]], case {{%[0-9]+}}: [[FALSEBB:bb[0-9]+]]
//
// CHECK: [[TRUEBB]]:
// CHECK-NEXT: br [[ENDBB:bb[0-9]+]]
//
// CHECK: [[FALSEBB]]:
// CHECK-NEXT: br [[ENDBB]]
//
// CHECK: [[ENDBB]]:
// CHECK: [[ONELIT:%[0-9]+]] = integer_literal $Builtin.IntLiteral, 1
// CHECK: [[ONE:%[0-9]+]] = apply {{%[0-9]+}}([[ONELIT]], {{%[0-9]+}})
// CHECK: store [[ONE]] to [trivial] [[RESULT]] : $*Int
// CHECK: [[VAL:%[0-9]+]] = load [trivial] [[RESULT]] : $*Int
// CHECK: [[VAL_RESULT:%[0-9]+]] = move_value [var_decl] [[VAL]] : $Int
// CHECK: dealloc_stack [[RESULT]] : $*Int
// CHECK: return [[VAL_RESULT:[%0-9]+]] : $Int
func testFallthrough3() throws -> Int {
switch Bool.random() {
case true:
switch Bool.random() {
case true:
if .random() {
fallthrough
}
throw Err()
case false:
1
}
case false:
0
}
}
func testClosure() throws -> Int {
let fn = {
switch Bool.random() {

View File

@@ -147,7 +147,7 @@ func testReturn1() -> Int {
try throwsError()
} catch {
if .random() {
return 0 // expected-error {{cannot 'return' in 'do-catch' when used as expression}}
return 0 // expected-error {{cannot use 'return' to transfer control out of 'do-catch' expression}}
}
then 0
}

View File

@@ -340,7 +340,7 @@ struct TestFailableInit {
let y = if x {
0
} else {
return nil // expected-error {{cannot 'return' in 'if' when used as expression}}
return nil // expected-error {{cannot use 'return' to transfer control out of 'if' expression}}
}
_ = y
}
@@ -605,16 +605,16 @@ func returnBranches1() -> Int {
func returnBranchVoid() {
return if .random() { return } else { return () }
// expected-error@-1 2{{cannot 'return' in 'if' when used as expression}}
// expected-error@-1 2{{cannot use 'return' to transfer control out of 'if' expression}}
}
func returnBranchBinding() -> Int {
let x = if .random() {
// expected-warning@-1 {{constant 'x' inferred to have type 'Void', which may be unexpected}}
// expected-note@-2 {{add an explicit type annotation to silence this warning}}
return 0 // expected-error {{cannot 'return' in 'if' when used as expression}}
return 0 // expected-error {{cannot use 'return' to transfer control out of 'if' expression}}
} else {
return 1 // expected-error {{cannot 'return' in 'if' when used as expression}}
return 1 // expected-error {{cannot use 'return' to transfer control out of 'if' expression}}
}
return x // expected-error {{cannot convert return expression of type 'Void' to return type 'Int'}}
}
@@ -648,9 +648,9 @@ func returnBranches5() throws -> Int {
let i = if .random() {
// expected-warning@-1 {{constant 'i' inferred to have type 'Void', which may be unexpected}}
// expected-note@-2 {{add an explicit type annotation to silence this warning}}
return 0 // expected-error {{cannot 'return' in 'if' when used as expression}}
return 0 // expected-error {{cannot use 'return' to transfer control out of 'if' expression}}
} else {
return 1 // expected-error {{cannot 'return' in 'if' when used as expression}}
return 1 // expected-error {{cannot use 'return' to transfer control out of 'if' expression}}
}
let j = if .random() {
// expected-warning@-1 {{constant 'j' inferred to have type 'Void', which may be unexpected}}
@@ -702,7 +702,7 @@ func returnBranches6PoundIf2() -> Int {
func returnBranches7() -> Int {
let i = if .random() {
print("hello")
return 0 // expected-error {{cannot 'return' in 'if' when used as expression}}
return 0 // expected-error {{cannot use 'return' to transfer control out of 'if' expression}}
} else {
1
}
@@ -710,7 +710,7 @@ func returnBranches7() -> Int {
}
func returnBranches8() -> Int {
let i = if .random() { return 1 } else { 0 } // expected-error {{cannot 'return' in 'if' when used as expression}}
let i = if .random() { return 1 } else { 0 } // expected-error {{cannot use 'return' to transfer control out of 'if' expression}}
return i
}
@@ -914,7 +914,7 @@ func break1() -> Int {
switch true {
case true:
let j = if .random() {
break // expected-error {{cannot 'break' in 'if' when used as expression}}
break // expected-error {{cannot use 'break' to transfer control out of 'if' expression}}
} else {
0
}
@@ -927,7 +927,7 @@ func break1() -> Int {
func continue1() -> Int {
for _ in 0 ... 5 {
let i = if true { continue } else { 1 }
// expected-error@-1 {{cannot 'continue' in 'if' when used as expression}}
// expected-error@-1 {{cannot use 'continue' to transfer control out of 'if' expression}}
return i
}
}
@@ -941,7 +941,7 @@ func return1() -> Int {
while true {
switch 0 {
default:
return 0 // expected-error {{cannot 'return' in 'if' when used as expression}}
return 0 // expected-error {{cannot use 'return' to transfer control out of 'if' expression}}
}
}
}
@@ -987,6 +987,63 @@ func return4() throws -> Int {
return i
}
// https://github.com/swiftlang/swift/issues/75880
func fallthrough1() throws {
switch Bool.random() {
case true:
let _ = if .random() {
if .random () {
fallthrough // expected-error {{cannot use 'fallthrough' to transfer control out of 'if' expression}}
}
throw Err()
} else {
0
}
case false:
break
}
}
func fallthrough2() throws -> Int {
let x = switch Bool.random() {
case true:
if .random() {
if .random () {
fallthrough // expected-error {{cannot use 'fallthrough' to transfer control out of 'if' expression}}
}
throw Err()
} else {
0
}
case false:
1
}
return x
}
func fallthrough3() -> Int {
let x = switch Bool.random() {
case true:
if .random() {
fallthrough // expected-error {{cannot use 'fallthrough' to transfer control out of 'if' expression}}
} else {
0
}
case false:
1
}
return x
}
func fallthrough4() -> Int {
let x = if .random() {
fallthrough // expected-error {{'fallthrough' is only allowed inside a switch}}
} else {
0
}
return x
}
// MARK: Effect specifiers
func tryIf1() -> Int {

View File

@@ -400,7 +400,7 @@ struct TestFailableInit {
case true:
0
case false:
return nil // expected-error {{cannot 'return' in 'switch' when used as expression}}
return nil // expected-error {{cannot use 'return' to transfer control out of 'switch' expression}}
}
_ = y
}
@@ -757,7 +757,7 @@ func returnBranches1() -> Int {
func returnBranchVoid() {
return switch Bool.random() { case true: return case false: return () }
// expected-error@-1 2{{cannot 'return' in 'switch' when used as expression}}
// expected-error@-1 2{{cannot use 'return' to transfer control out of 'switch' expression}}
}
func returnBranchBinding() -> Int {
@@ -765,9 +765,9 @@ func returnBranchBinding() -> Int {
// expected-warning@-1 {{constant 'x' inferred to have type 'Void', which may be unexpected}}
// expected-note@-2 {{add an explicit type annotation to silence this warning}}
case true:
return 0 // expected-error {{cannot 'return' in 'switch' when used as expression}}
return 0 // expected-error {{cannot use 'return' to transfer control out of 'switch' expression}}
case false:
return 1 // expected-error {{cannot 'return' in 'switch' when used as expression}}
return 1 // expected-error {{cannot use 'return' to transfer control out of 'switch' expression}}
}
return x // expected-error {{cannot convert return expression of type 'Void' to return type 'Int'}}
}
@@ -807,9 +807,9 @@ func returnBranches5() -> Int {
// expected-warning@-1 {{constant 'i' inferred to have type 'Void', which may be unexpected}}
// expected-note@-2 {{add an explicit type annotation to silence this warning}}
case true:
return 0 // expected-error {{cannot 'return' in 'switch' when used as expression}}
return 0 // expected-error {{cannot use 'return' to transfer control out of 'switch' expression}}
case false:
return 1 // expected-error {{cannot 'return' in 'switch' when used as expression}}
return 1 // expected-error {{cannot use 'return' to transfer control out of 'switch' expression}}
}
return i // expected-error {{cannot convert return expression of type 'Void' to return type 'Int'}}
}
@@ -820,7 +820,7 @@ func returnBranches6() -> Int {
case true:
print("hello")
0 // expected-warning {{integer literal is unused}}
// expected-error@-1 {{non-expression branch of 'switch' expression may only end with a 'throw'}}
// expected-error@-1 {{non-expression branch of 'switch' expression may only end with a 'throw' or 'fallthrough'}}
case false:
1
}
@@ -835,7 +835,7 @@ func returnBranches6PoundIf() -> Int {
print("hello")
0 // expected-warning {{integer literal is unused}}
#endif
// expected-error@-1 {{non-expression branch of 'switch' expression may only end with a 'throw'}}
// expected-error@-1 {{non-expression branch of 'switch' expression may only end with a 'throw' or 'fallthrough'}}
case false:
1
}
@@ -850,7 +850,7 @@ func returnBranches6PoundIf2() -> Int {
print("hello")
0
#endif
// expected-error@-1 {{non-expression branch of 'switch' expression may only end with a 'throw'}}
// expected-error@-1 {{non-expression branch of 'switch' expression may only end with a 'throw' or 'fallthrough'}}
case false:
1
}
@@ -861,7 +861,7 @@ func returnBranches7() -> Int {
let i = switch Bool.random() {
case true:
print("hello")
return 0 // expected-error {{cannot 'return' in 'switch' when used as expression}}
return 0 // expected-error {{cannot use 'return' to transfer control out of 'switch' expression}}
case false:
1
}
@@ -871,7 +871,7 @@ func returnBranches7() -> Int {
func returnBranches8() -> Int {
let i = switch Bool.random() {
case true:
return 1 // expected-error {{cannot 'return' in 'switch' when used as expression}}
return 1 // expected-error {{cannot use 'return' to transfer control out of 'switch' expression}}
case false:
0
}
@@ -882,7 +882,7 @@ func returnBranches9() -> Int {
let i = switch Bool.random() {
case true:
print("hello")
if .random() {} // expected-error {{non-expression branch of 'switch' expression may only end with a 'throw'}}
if .random() {} // expected-error {{non-expression branch of 'switch' expression may only end with a 'throw' or 'fallthrough'}}
case false:
1
}
@@ -898,7 +898,7 @@ func returnBranches10() -> Int {
0 // expected-warning {{integer literal is unused}}
case false:
2 // expected-warning {{integer literal is unused}}
} // expected-error {{non-expression branch of 'switch' expression may only end with a 'throw'}}
} // expected-error {{non-expression branch of 'switch' expression may only end with a 'throw' or 'fallthrough'}}
case false:
1
}
@@ -914,7 +914,7 @@ func returnBranches11() -> Int {
"" // expected-warning {{string literal is unused}}
case false:
2 // expected-warning {{integer literal is unused}}
} // expected-error {{non-expression branch of 'switch' expression may only end with a 'throw'}}
} // expected-error {{non-expression branch of 'switch' expression may only end with a 'throw' or 'fallthrough'}}
case false:
1
}
@@ -1059,7 +1059,7 @@ func testPoundIfBranch3() -> Int {
#if false
0
#endif
// expected-error@-1 {{non-expression branch of 'switch' expression may only end with a 'throw'}}
// expected-error@-1 {{non-expression branch of 'switch' expression may only end with a 'throw' or 'fallthrough'}}
case false:
0
}
@@ -1100,7 +1100,7 @@ func testPoundIfBranch6() -> Int {
0
#endif
0 // expected-warning {{integer literal is unused}}
// expected-error@-1 {{non-expression branch of 'switch' expression may only end with a 'throw'}}
// expected-error@-1 {{non-expression branch of 'switch' expression may only end with a 'throw' or 'fallthrough'}}
case false:
1
}
@@ -1173,13 +1173,55 @@ func fallthrough2() -> Int {
if .random() {
fallthrough
}
return 1 // expected-error {{cannot 'return' in 'switch' when used as expression}}
return 1 // expected-error {{cannot use 'return' to transfer control out of 'switch' expression}}
case false:
0
}
return x
}
func fallthrough3() -> Int {
let x = switch true {
case true:
fallthrough
case false:
0
}
return x
}
func fallthrough4() -> Int {
let x = switch true {
case true:
fallthrough
return 0 // expected-error {{cannot use 'return' to transfer control out of 'switch' expression}}
case false:
0
}
return x
}
func fallthrough5() -> Int {
let x = switch true {
case true:
fallthrough
print(0) // expected-error {{non-expression branch of 'switch' expression may only end with a 'throw' or 'fallthrough'}}
case false:
0
}
return x
}
func fallthrough6() -> Int {
let x = switch true {
case true:
0
case false:
fallthrough // expected-error {{'fallthrough' without a following 'case' or 'default' block}}
}
return x
}
func breakAfterNeverExpr() -> String {
// We avoid turning this into a switch expression because of the 'break'.
switch Bool.random() {

View File

@@ -8,9 +8,9 @@
// Required for experimental features
// REQUIRES: asserts
func testDefer(_ x: Bool) -> Int {
func testDeferIf(_ x: Bool) -> Int {
defer {
print("defer fn")
print("defer testDeferIf")
}
let x = if x {
defer {
@@ -24,9 +24,55 @@ func testDefer(_ x: Bool) -> Int {
print("after if")
return x
}
_ = testDefer(true)
_ = testDeferIf(true)
// CHECK: enter if
// CHECK-NEXT: defer if
// CHECK-NEXT: after if
// CHECK-NEXT: defer fn
// CHECK-NEXT: defer testDeferIf
func testDeferSwitch(_ x: Bool) -> Int {
defer {
print("defer testDeferSwitch")
}
let x = switch x {
case true:
defer {
print("defer case true")
}
print("enter case true")
fallthrough
case false:
defer {
print("defer case false")
}
print("enter case false")
then 1
}
print("after switch")
return x
}
_ = testDeferSwitch(true)
// CHECK: enter case true
// CHECK-NEXT: defer case true
// CHECK-NEXT: enter case false
// CHECK-NEXT: defer case false
// CHECK-NEXT: after switch
// CHECK-NEXT: defer testDeferSwitch
func testFallthrough(_ x: Bool) -> Int {
var z = 0
let y: Int
let x = switch x {
case true:
z = 1
fallthrough
case false:
y = 2
then 3
}
return x + y + z
}
print("fallthrough: \(testFallthrough(true))")
// CHECK: fallthrough: 6