Skip to content

Commit b275edd

Browse files
authored
Merge pull request #17967 from vguerra/SR7307
[TypeChecker] SR-7307 : Warn when a block contains defer as last statement
2 parents 10820f3 + da3149d commit b275edd

File tree

8 files changed

+63
-10
lines changed

8 files changed

+63
-10
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3033,6 +3033,10 @@ ERROR(jump_out_of_defer,none,
30333033
"'%0' cannot transfer control out of a defer statement",
30343034
(StringRef))
30353035

3036+
WARNING(defer_stmt_at_block_end,none,
3037+
"'defer' statement before end of scope always executes immediately; "
3038+
"replace with 'do' statement to silence this warning", ())
3039+
30363040
ERROR(return_invalid_outside_func,none,
30373041
"return invalid outside of a func", ())
30383042

lib/Sema/TypeCheckStmt.cpp

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,9 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
313313
/// expressions are not discarded.
314314
bool IsREPL;
315315

316+
/// Used to distinguish the first BraceStmt that starts a TopLevelCodeDecl.
317+
bool IsBraceStmtFromTopLevelDecl;
318+
316319
struct AddLabeledStmt {
317320
StmtChecker &SC;
318321
AddLabeledStmt(StmtChecker &SC, LabeledStmt *LS) : SC(SC) {
@@ -352,16 +355,21 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
352355
};
353356

354357
StmtChecker(TypeChecker &TC, AbstractFunctionDecl *AFD)
355-
: TC(TC), TheFunc(AFD), DC(AFD), IsREPL(false) { }
358+
: TC(TC), TheFunc(AFD), DC(AFD), IsREPL(false),
359+
IsBraceStmtFromTopLevelDecl(false) {}
356360

357361
StmtChecker(TypeChecker &TC, ClosureExpr *TheClosure)
358-
: TC(TC), TheFunc(TheClosure), DC(TheClosure), IsREPL(false) { }
362+
: TC(TC), TheFunc(TheClosure), DC(TheClosure), IsREPL(false),
363+
IsBraceStmtFromTopLevelDecl(false) {}
359364

360365
StmtChecker(TypeChecker &TC, DeclContext *DC)
361-
: TC(TC), TheFunc(), DC(DC), IsREPL(false) {
366+
: TC(TC), TheFunc(), DC(DC), IsREPL(false),
367+
IsBraceStmtFromTopLevelDecl(false) {
362368
if (const SourceFile *SF = DC->getParentSourceFile())
363369
if (SF->Kind == SourceFileKind::REPL)
364370
IsREPL = true;
371+
372+
IsBraceStmtFromTopLevelDecl = isa<TopLevelCodeDecl>(DC);
365373
}
366374

367375
//===--------------------------------------------------------------------===//
@@ -550,7 +558,7 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
550558
Expr *theCall = DS->getCallExpr();
551559
TC.typeCheckExpression(theCall, DC);
552560
DS->setCallExpr(theCall);
553-
561+
554562
return DS;
555563
}
556564

@@ -1397,6 +1405,21 @@ void TypeChecker::checkIgnoredExpr(Expr *E) {
13971405

13981406
Stmt *StmtChecker::visitBraceStmt(BraceStmt *BS) {
13991407
const SourceManager &SM = TC.Context.SourceMgr;
1408+
1409+
// Diagnose defer statement being last one in block (only if
1410+
// BraceStmt does not start a TopLevelDecl).
1411+
if (IsBraceStmtFromTopLevelDecl) {
1412+
IsBraceStmtFromTopLevelDecl = false;
1413+
} else if (BS->getNumElements() > 0) {
1414+
if (auto stmt =
1415+
BS->getElement(BS->getNumElements() - 1).dyn_cast<Stmt *>()) {
1416+
if (auto deferStmt = dyn_cast<DeferStmt>(stmt)) {
1417+
TC.diagnose(deferStmt->getStartLoc(), diag::defer_stmt_at_block_end)
1418+
.fixItReplace(deferStmt->getStartLoc(), "do");
1419+
}
1420+
}
1421+
}
1422+
14001423
for (auto &elem : BS->getElements()) {
14011424
if (auto *SubExpr = elem.dyn_cast<Expr*>()) {
14021425
SourceLoc Loc = SubExpr->getStartLoc();
@@ -1452,7 +1475,7 @@ Stmt *StmtChecker::visitBraceStmt(BraceStmt *BS) {
14521475

14531476
TC.typeCheckDecl(SubDecl);
14541477
}
1455-
1478+
14561479
return BS;
14571480
}
14581481

test/SILGen/statements.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ func defer_in_closure_in_generic<T>(_ x: T) {
524524
// CHECK-LABEL: sil private @$S10statements017defer_in_closure_C8_genericyyxlFyycfU_ : $@convention(thin) <T> () -> ()
525525
_ = {
526526
// CHECK-LABEL: sil private @$S10statements017defer_in_closure_C8_genericyyxlFyycfU_6$deferL_yylF : $@convention(thin) <T> () -> ()
527-
defer { generic_callee_1(T.self) }
527+
defer { generic_callee_1(T.self) } // expected-warning {{'defer' statement before end of scope always executes immediately}}{{5-10=do}}
528528
}
529529
}
530530

@@ -537,7 +537,7 @@ func defer_mutable(_ x: Int) {
537537
// CHECK: function_ref @$S10statements13defer_mutableyySiF6$deferL_yyF : $@convention(thin) (@inout_aliasable Int) -> ()
538538
// CHECK-NOT: [[BOX]]
539539
// CHECK: destroy_value [[BOX]]
540-
defer { _ = x }
540+
defer { _ = x } // expected-warning {{'defer' statement before end of scope always executes immediately}}{{3-8=do}}
541541
}
542542

543543
protocol StaticFooProtocol { static func foo() }

test/SILOptimizer/definite_init_diagnostics_globals.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ defer { print(y) } // expected-error {{constant 'y' used in defer before being i
2121
// Test top-level functions.
2222

2323
func testFunc() { // expected-error {{variable 'x' used by function definition before being initialized}}
24-
defer { print(x) }
24+
defer { print(x) } // expected-warning {{'defer' statement before end of scope always executes immediately}}{{3-8=do}}
2525
}
2626

2727
// Test top-level closures.

test/SILOptimizer/unreachable_code.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ func deferTryNoReturn() throws {
328328
}
329329

330330
func noReturnInDefer() {
331-
defer {
331+
defer { // expected-warning {{'defer' statement before end of scope always executes immediately}}{{3-8=do}}
332332
_ = Lisp()
333333
die() // expected-note {{a call to a never-returning function}}
334334
die() // expected-warning {{will never be executed}}

test/Sema/diag_defer_block_end.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// RUN: %target-swift-frontend -typecheck -verify %s
2+
3+
let x = 1
4+
let y = 2
5+
if (x > y) {
6+
defer { // expected-warning {{'defer' statement before end of scope always executes immediately}}{{5-10=do}}
7+
print("not so useful defer stmt.")
8+
}
9+
}
10+
11+
func sr7307(_ value: Bool) {
12+
let negated = !value
13+
defer { // expected-warning {{'defer' statement before end of scope always executes immediately}}{{5-10=do}}
14+
print("negated value is {negated}")
15+
}
16+
}
17+
18+
sr7307(true)
19+
20+
defer { // No note
21+
print("end of program.")
22+
}
23+

test/stmt/statements.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,14 +342,17 @@ func test_defer(_ a : Int) {
342342

343343
// Not ok.
344344
while false { defer { break } } // expected-error {{'break' cannot transfer control out of a defer statement}}
345+
// expected-warning@-1 {{'defer' statement before end of scope always executes immediately}}{{17-22=do}}
345346
defer { return } // expected-error {{'return' cannot transfer control out of a defer statement}}
347+
// expected-warning@-1 {{'defer' statement before end of scope always executes immediately}}{{3-8=do}}
346348
}
347349

348350
class SomeTestClass {
349351
var x = 42
350352

351353
func method() {
352354
defer { x = 97 } // self. not required here!
355+
// expected-warning@-1 {{'defer' statement before end of scope always executes immediately}}{{5-10=do}}
353356
}
354357
}
355358

test/stmt/yield.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func call_yield() {
8282
struct YieldInDefer {
8383
var property: String {
8484
_read {
85-
defer {
85+
defer { // expected-warning {{'defer' statement before end of scope always executes immediately}}{{7-12=do}}
8686
// FIXME: this recovery is terrible
8787
yield ""
8888
// expected-error@-1 {{expression resolves to an unused function}}

0 commit comments

Comments
 (0)