mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Merge pull request #75891 from hamishknight/fellthrough
[Sema] Improve handling of `fallthrough` in `if`/`switch` expressions
This commit is contained in:
@@ -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:)")
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1794,7 +1794,7 @@ private:
|
||||
}
|
||||
|
||||
ASTNode visitFallthroughStmt(FallthroughStmt *fallthroughStmt) {
|
||||
if (checkFallthroughStmt(context.getAsDeclContext(), fallthroughStmt))
|
||||
if (checkFallthroughStmt(fallthroughStmt))
|
||||
hadError = true;
|
||||
return fallthroughStmt;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 ()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 } })
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user