Skip to content

[TypeChecker] SR-7307 : Warn when a block contains defer as last statement #17967

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -3033,6 +3033,10 @@ ERROR(jump_out_of_defer,none,
"'%0' cannot transfer control out of a defer statement",
(StringRef))

WARNING(defer_stmt_at_block_end,none,
"'defer' statement before end of scope always executes immediately; "
"replace with 'do' statement to silence this warning", ())

ERROR(return_invalid_outside_func,none,
"return invalid outside of a func", ())

Expand Down
33 changes: 28 additions & 5 deletions lib/Sema/TypeCheckStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,9 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
/// expressions are not discarded.
bool IsREPL;

/// Used to distinguish the first BraceStmt that starts a TopLevelCodeDecl.
bool IsBraceStmtFromTopLevelDecl;

struct AddLabeledStmt {
StmtChecker &SC;
AddLabeledStmt(StmtChecker &SC, LabeledStmt *LS) : SC(SC) {
Expand Down Expand Up @@ -352,16 +355,21 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
};

StmtChecker(TypeChecker &TC, AbstractFunctionDecl *AFD)
: TC(TC), TheFunc(AFD), DC(AFD), IsREPL(false) { }
: TC(TC), TheFunc(AFD), DC(AFD), IsREPL(false),
IsBraceStmtFromTopLevelDecl(false) {}

StmtChecker(TypeChecker &TC, ClosureExpr *TheClosure)
: TC(TC), TheFunc(TheClosure), DC(TheClosure), IsREPL(false) { }
: TC(TC), TheFunc(TheClosure), DC(TheClosure), IsREPL(false),
IsBraceStmtFromTopLevelDecl(false) {}

StmtChecker(TypeChecker &TC, DeclContext *DC)
: TC(TC), TheFunc(), DC(DC), IsREPL(false) {
: TC(TC), TheFunc(), DC(DC), IsREPL(false),
IsBraceStmtFromTopLevelDecl(false) {
if (const SourceFile *SF = DC->getParentSourceFile())
if (SF->Kind == SourceFileKind::REPL)
IsREPL = true;

IsBraceStmtFromTopLevelDecl = isa<TopLevelCodeDecl>(DC);
}

//===--------------------------------------------------------------------===//
Expand Down Expand Up @@ -550,7 +558,7 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
Expr *theCall = DS->getCallExpr();
TC.typeCheckExpression(theCall, DC);
DS->setCallExpr(theCall);

return DS;
}

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

Stmt *StmtChecker::visitBraceStmt(BraceStmt *BS) {
const SourceManager &SM = TC.Context.SourceMgr;

// Diagnose defer statement being last one in block (only if
// BraceStmt does not start a TopLevelDecl).
if (IsBraceStmtFromTopLevelDecl) {
IsBraceStmtFromTopLevelDecl = false;
} else if (BS->getNumElements() > 0) {
if (auto stmt =
BS->getElement(BS->getNumElements() - 1).dyn_cast<Stmt *>()) {
if (auto deferStmt = dyn_cast<DeferStmt>(stmt)) {
TC.diagnose(deferStmt->getStartLoc(), diag::defer_stmt_at_block_end)
.fixItReplace(deferStmt->getStartLoc(), "do");
}
}
}

for (auto &elem : BS->getElements()) {
if (auto *SubExpr = elem.dyn_cast<Expr*>()) {
SourceLoc Loc = SubExpr->getStartLoc();
Expand Down Expand Up @@ -1452,7 +1475,7 @@ Stmt *StmtChecker::visitBraceStmt(BraceStmt *BS) {

TC.typeCheckDecl(SubDecl);
}

return BS;
}

Expand Down
4 changes: 2 additions & 2 deletions test/SILGen/statements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ func defer_in_closure_in_generic<T>(_ x: T) {
// CHECK-LABEL: sil private @$S10statements017defer_in_closure_C8_genericyyxlFyycfU_ : $@convention(thin) <T> () -> ()
_ = {
// CHECK-LABEL: sil private @$S10statements017defer_in_closure_C8_genericyyxlFyycfU_6$deferL_yylF : $@convention(thin) <T> () -> ()
defer { generic_callee_1(T.self) }
defer { generic_callee_1(T.self) } // expected-warning {{'defer' statement before end of scope always executes immediately}}{{5-10=do}}
}
}

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

protocol StaticFooProtocol { static func foo() }
Expand Down
2 changes: 1 addition & 1 deletion test/SILOptimizer/definite_init_diagnostics_globals.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ defer { print(y) } // expected-error {{constant 'y' used in defer before being i
// Test top-level functions.

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

// Test top-level closures.
Expand Down
2 changes: 1 addition & 1 deletion test/SILOptimizer/unreachable_code.swift
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ func deferTryNoReturn() throws {
}

func noReturnInDefer() {
defer {
defer { // expected-warning {{'defer' statement before end of scope always executes immediately}}{{3-8=do}}
_ = Lisp()
die() // expected-note {{a call to a never-returning function}}
die() // expected-warning {{will never be executed}}
Expand Down
23 changes: 23 additions & 0 deletions test/Sema/diag_defer_block_end.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// RUN: %target-swift-frontend -typecheck -verify %s
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can take care of this in a future commit, but this could actually be // RUN: %target-typecheck-verify-swift without explicit options or %s.


let x = 1
let y = 2
if (x > y) {
defer { // expected-warning {{'defer' statement before end of scope always executes immediately}}{{5-10=do}}
print("not so useful defer stmt.")
}
}

func sr7307(_ value: Bool) {
let negated = !value
defer { // expected-warning {{'defer' statement before end of scope always executes immediately}}{{5-10=do}}
print("negated value is {negated}")
}
}

sr7307(true)

defer { // No note
print("end of program.")
}

3 changes: 3 additions & 0 deletions test/stmt/statements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -342,14 +342,17 @@ func test_defer(_ a : Int) {

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

class SomeTestClass {
var x = 42

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

Expand Down
2 changes: 1 addition & 1 deletion test/stmt/yield.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func call_yield() {
struct YieldInDefer {
var property: String {
_read {
defer {
defer { // expected-warning {{'defer' statement before end of scope always executes immediately}}{{7-12=do}}
// FIXME: this recovery is terrible
yield ""
// expected-error@-1 {{expression resolves to an unused function}}
Expand Down