-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly some formatting nits. My two substantive pieces of feedback are:
The diagnostic should, I think, be more descriptive; a user who has the misunderstanding described in the linked bug won't be educated by seeing a diagnostic that says "pointless." Maybe something along the lines of saying that the body will not be deferred because it's already at the end of the block.
My second concern is how this interacts with initializers. As you know, setting a property inside an initializer doesn't invoke any didSet or willSet routines. However, whether a bug or no, I recall there's been some issue where a defer statement can be used inside an initializer to get around that. I don't recall if that's still the case. But, if so, a defer statement at the end of an initializer isn't actually pointless as removing it would change the observed behavior.
@@ -54,6 +54,9 @@ NOTE(previous_decldef,none, | |||
NOTE(brace_stmt_suggest_do,none, | |||
"did you mean to use a 'do' statement?", ()) | |||
|
|||
NOTE(defer_stmt_at_blockend,none, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: "block_end" (two words).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in next commit
@@ -54,6 +54,9 @@ NOTE(previous_decldef,none, | |||
NOTE(brace_stmt_suggest_do,none, | |||
"did you mean to use a 'do' statement?", ()) | |||
|
|||
NOTE(defer_stmt_at_blockend,none, | |||
"defer statement at the end of block is pointless", ()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pointless because...? It's important to give a little more explanation here, I think. Also, stylistically, "defer" should be surrounded by single quotes, as shown above with "a 'do' statement".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest something like:
'defer' at the end of its scope is redundant and will execute immediately
Probably best for a follow up PR, but it would be nice to have a fixit that simply removes the defer
and braces.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems it'd be simpler just to suggest do
in place of defer
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks @xwu and @jszumski for the reviews. I improved the note message as you suggested in the following commit.
I thought as well about the fix-it from the beginning but did not have the time to see how to implement it, I am still getting familiar with the code base :). As @xwu, probably the easier/simpler thing would be to suggest a do
. Will look into it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lib/Sema/TypeCheckStmt.cpp
Outdated
@@ -352,13 +355,13 @@ 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) { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formatting nit: please wrap to 80 characters per Clang style.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in next commit.
lib/Sema/TypeCheckStmt.cpp
Outdated
@@ -1333,6 +1336,19 @@ 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 ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formatting nit: please end comments with a period here and elsewhere; also, no spaces or capitalization inside parentheses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in next commit.
lib/Sema/TypeCheckStmt.cpp
Outdated
|
||
StmtChecker(TypeChecker &TC, DeclContext *DC) | ||
: TC(TC), TheFunc(), DC(DC), IsREPL(false) { | ||
: TC(TC), TheFunc(), DC(DC), IsREPL(false), IsBraceStmtFromTopLevelDecl(true) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not necessarily true
. You'll want to ask isa<TopLevelCodeDecl>(DC)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately I can not relay on isa<TopLevelCodeDecl>(DC)
because in the following code snippet, it would return true
when visiting the {
that opens the if
s then
block:
let x = 1
let y = 3
if (x > y) {
defer {
print("not so useful defer stmt.")
}
}
and I would like to diagnose this block.
@xwu regarding using |
I'm not aware of other cases where we emit a note in isolation as opposed to being attached to a warning or error. Do you know if there is precedence for this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a test that the fix-it is applied correctly.
Oh, good catch. No, that is never correct - and I thought the diagnostic engine would trap on that. This should be a warning. |
@@ -54,6 +54,10 @@ NOTE(previous_decldef,none, | |||
NOTE(brace_stmt_suggest_do,none, | |||
"did you mean to use a 'do' statement?", ()) | |||
|
|||
NOTE(defer_stmt_at_block_end,none, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a warning. Notes are always attached to warnings or errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fixed in the next commit :)
hi @rudkx! .. thank you for catching this. In my last commit I converted the NOTE into a WARNING and updated the test file to check for the fix-it! |
@swift-ci Please smoke test |
1 similar comment
@swift-ci Please smoke test |
a test that has been added recently fails this time :( ( https://ci.swift.org/job/swift-PR-Linux-smoke-test/8474/testReport/junit/Swift(linux-x86_64)/stmt/yield_swift/ ). My bad, I did not rebase locally before running tests. fix is coming :) |
Updating test as well to check that the fix-it is applied correctly.
Usage of 'defer' now emits a warning in some places in the tests, so adding the corresponding 'expected-warning'.
@@ -54,6 +54,10 @@ NOTE(previous_decldef,none, | |||
NOTE(brace_stmt_suggest_do,none, | |||
"did you mean to use a 'do' statement?", ()) | |||
|
|||
WARNING(defer_stmt_at_block_end,none, | |||
"'defer' at the end of its scope is redundant and will execute immediately; " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jrose-apple wrote an excellent guide on Swift diagnostic messages. Some points to consider here include using a terse style (known as reduced written register), phrasing as a rule, and matching the tone of other diagnostics. So perhaps:
'defer' statement immediately before end of scope always executes immediately; replace with 'do' statement to silence this warning
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you for the pointer to the Diagnostics guide... useful stuff 👍 .. and yes the message you suggest definitely reads better. Will update it in next commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning message improved in ffd1366
@@ -54,6 +54,10 @@ NOTE(previous_decldef,none, | |||
NOTE(brace_stmt_suggest_do,none, | |||
"did you mean to use a 'do' statement?", ()) | |||
|
|||
WARNING(defer_stmt_at_block_end,none, | |||
"'defer' at the end of its scope is redundant and will execute immediately; " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: The diagnostic definition is better placed within DiagnosticsSema.def as it's only emitted from Sema. DiagnosticsCommon is for common diagnostics that are emitted from separate parts of the compiler (e.g Parse and Sema).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good catch. Yes, this belongs in DiagnosticsSema.def
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you for catching this ... fixed in ffd1366
- moving WARNING definition from DiagnosticsCommon.def to DiagnosticsSema.def - improving WARNING message to better align with guidelines described in docs/Diagnostics.md as suggested by @xwu. - adapting expected warning message in tests.
test/stmt/yield.swift
Outdated
@@ -82,7 +82,7 @@ func call_yield() { | |||
struct YieldInDefer { | |||
var property: String { | |||
_read { | |||
defer { | |||
defer { // expectd-warning {{'defer' statement before end of scope always executes immediately}}{{7-12=do}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: expectd
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😢 ... fixed on ab513c8
lib/Sema/TypeCheckStmt.cpp
Outdated
|
||
StmtChecker(TypeChecker &TC, DeclContext *DC) | ||
: TC(TC), TheFunc(), DC(DC), IsREPL(false) { | ||
: TC(TC), TheFunc(), DC(DC), IsREPL(false), | ||
IsBraceStmtFromTopLevelDecl(true) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I missed this earlier. Rather than setting this to true
, can you instead check getContextKind()
of the DeclContext
that is passed in. That is less fragile, since as written this relies on the fact that the only place we currently use this constructor is when we're passing in a top level decl context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is kind of similar to what Robert suggested above about using isa<TopLevelCodeDecl>(DC)
, to what I responded:
Unfortunately I can not relay on
isa<TopLevelCodeDecl>(DC)
because in the following code snippet, it would returntrue
when visiting the{
that opens theif
sthen
block:
let x = 1
let y = 3
if (x > y) {
defer {
print("not so useful defer stmt.")
}
}
and I would like to diagnose this block.
when visiting the open brace of the if DC->getContextKind()
returns TopLevelCodeDecl
, so I can't rely on that 😞
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For that you should have an outer BraceStmt
, with an IfStmt
nested within it, and a BraceStmt
nested under it. So I would expect that when you hit the outermost BraceStmt
you would set IsBraceStmtFromTopLevelDecl
to false like you are now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry.. I completely missed your point. I see what you mean now, I should base the initialization of the IsBraceStmtFromTopLevelDecl on DC->getContextKind()
. Fixed it on da3149d
If DeclContext's kind is TopLevelCodeDecl we are sure to be visiting a BraceStmt that opens a TopLevelDecl.
@@ -0,0 +1,23 @@ | |||
// RUN: %target-swift-frontend -typecheck -verify %s |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
@swift-ci Please smoke test |
thanks @rudkx ! |
This PR adds a diagnostic (
NOTEWARNING ) when a defer statement is the last statement within a block of code. The diagnostic is implemented as follows:BraceStmt
simply check if last statement of it's elements is aDeferStmt
. If that is the case, emit diagnostic.IsBraceStmtFromTopLevelDecl
) has been added to theStmtChecker
in order to keep track of whether theBraceStmt
being checked belongs to aTopLevelCodeDecl
or not. This is needed to avoid diagnosing everydefer
statement found at the top level. (check discussion in : SR-7307)defer
statement that appears last at the top level ( not sure if this is worth adding ).Resolves SR-7307.