Skip to content
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

[SR-7307] Warn when a block contains nothing but a defer statement #49855

Closed
jckarter opened this issue Mar 29, 2018 · 20 comments
Closed

[SR-7307] Warn when a block contains nothing but a defer statement #49855

jckarter opened this issue Mar 29, 2018 · 20 comments
Assignees
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself diagnostics QoI Bug: Diagnostics Quality of Implementation good first issue Good for newcomers

Comments

@jckarter
Copy link
Member

Previous ID SR-7307
Radar None
Original Reporter @jckarter
Type Bug
Status Closed
Resolution Done
Additional Detail from JIRA
Votes 1
Component/s Compiler
Labels Bug, DiagnosticsQoI, StarterBug
Assignee @vguerra
Priority Medium

md5: 9b4fc281235964efdf067b80f3a93de4

Issue Description:

A user used to Go's defer, or expecting Go-like behavior from first principles, may write something like this:

if x {
  defer { thing_i_only_want_to_happen_when_x() }
}

which is going to run thing_i_only_want_to_happen_when_x at the exit of the if scope rather than at the end of the function. If a block contains nothing but a `defer`, the defer is useless, and we should probably warn to that effect.

@swift-ci
Copy link
Collaborator

Comment by Piyush Verma (JIRA)

Hi All,
Is the scope of this bug:
1.) To warn when `defer` is the only statement in the block.
OR
2.) To warn in all cases when `defer` is at the end of a block.

@jckarter
Copy link
Member Author

I had only though of (1), but (2) seems generally useful too. Good idea piverma (JIRA User)!

@swift-ci
Copy link
Collaborator

Comment by Piyush Verma (JIRA)

@jckarter - I am interested in working on this Bug. Can I pick this as my starter bug?
P.S.: I am a beginner and this will be my first bug in the community.

@jckarter
Copy link
Member Author

Sure! Let us know if you need any help getting started.

@swift-ci
Copy link
Collaborator

Comment by Piyush Verma (JIRA)

@jckarter - I am facing an issue. How do i get the reference of the immediate parent closure of defer block. Similar to `Optional<AnyFunctionRef> TheFunc`

@vguerra
Copy link
Contributor

vguerra commented May 23, 2018

for the time being this bug is not assigned. I'll assign it to me and tackle it ( piverma (JIRA User), tell me if you still would like to work on this 😉 ).

@swift-ci
Copy link
Collaborator

Comment by Piyush Verma (JIRA)

Hi @vguerra ,
I needed some help regarding my above query.

@vguerra
Copy link
Contributor

vguerra commented Jul 9, 2018

hello piverma (JIRA User) and @jckarter

I have been trying to tackle this bug during the last weeks and the best I came up with, so far, is adding a check during the type checking phase. More specifically, when the BraceStmt is being type checked ( https://github.com/apple/swift/blob/master/lib/Sema/TypeCheckStmt.cpp#L1334 ).

My approach is very simple, emit a diagnostic message when a BrackeStmt encloses 1 element and this element is a DeferStmt. I.e.:

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

  if (BS->getNumElements() == 1) {
    if (auto stmt = BS->getElement(0).dyn_cast<Stmt*>()) {
      if (auto deferStmt = dyn_cast<DeferStmt>(stmt)) {
        TC.diagnose(deferStmt->getStartLoc(), diag::defer_stmt_suggest_useless);
      }
    }
  }
...

this seems to do the job, except in the case when defer statements appear at the top level of the file: E.g.:

using the following code:

defer {
    print("end of program.")
}

let x = 1
let y = 3

if (x > y) {
    defer {
        print("not so useful defer stmt.")
    }
}

would generate the following note messages:

$> ./swiftc ../../../../examples/defer.swift
../../../../examples/defer.swift:1:1: note: defer statement is useless here
defer {
^
../../../../examples/defer.swift:9:5: note: defer statement is useless here
    defer {
    ^

and this is due to the fact that all Stmt's at the top level of the file are enclosed within a BraceStmt. The AST of the code above looks as follows ( https://gist.github.com/vguerra/5cfe45b2dda873deefd3cc444fc8d507 ):

$> /swiftc -dump-ast ../../../../examples/defer.swift &> /tmp/ast.txt
(source_file
  (top_level_code_decl
    (brace_stmt
      (defer_stmt
        (func_decl implicit "$defer()" interface type='() -> ()' access=fileprivate
        ...
  (top_level_code_decl
    (brace_stmt
      (pattern_binding_decl
        (pattern_named type='Int' 'x')
        ...
  (top_level_code_decl
    (brace_stmt
      (pattern_binding_decl
        (pattern_named type='Int' 'y')
  (top_level_code_decl
    (brace_stmt
      (if_stmt
      ...

the first DeferStmt in the AST would trigger the check i added due to the fact that it is the only statement within the first BraceStmt

So what I am now trying to figure out is if there is a smarter way to check those BraceStmt at the top level of the file.

I thought of taking advantage of the fact that StmtChecker has a reference to the TypeChecker and DeclContext to differentiate the BraceStmt at the top level but did not succeed. Is there a way i could achieve that?

Or maybe I took the wrong approach to solve this and there is another easier or more straight forward way to implement this ❓ 🙂.

Any comments, suggestions and pointers are more than appreciated 😃

@swift-ci
Copy link
Collaborator

swift-ci commented Jul 9, 2018

Comment by Piyush Verma (JIRA)

Hi @vguerra ,
Great idea of implementing the check in visitBraceStmt. I find it better than the approach i was trying to implement. This approach solves condition (1). Can we have something like:

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_suggest_useless);
      }
    }
 }

to tackle condition (1) and (2) both.

@swift-ci
Copy link
Collaborator

swift-ci commented Jul 9, 2018

Comment by Piyush Verma (JIRA)

@vguerra, @jckarter,

TheFunc.hasValue()

Can this check not help in the case mentioned by @vguerra ? Since its null for top level code.

I mean:

if (TheFunc.hasValue() && 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_suggest_useless);
      }
    }
  }

@vguerra
Copy link
Contributor

vguerra commented Jul 9, 2018

hi! piverma (JIRA User)

indeed checking for the last element under the BraceStmt is much better and covers (1) and (2). Will do that 🙂.

Still, I am not sure how to solve the issue about DeferStmt being a top level decl. :/.

@vguerra
Copy link
Contributor

vguerra commented Jul 9, 2018

Oh nice! .. I did not notice before that {{TheFunc}} is null for those cases 🙂. @jckarter is it safe to base the check on that assumption?

@vguerra
Copy link
Contributor

vguerra commented Jul 9, 2018

hi piverma (JIRA User),

unfortunately I dont think checking TheFunc would help. I just checked locally and for both BraceStmts with a defer statements in my toy example

TheFunc.hasValue()

is False 🙁

@swift-ci
Copy link
Collaborator

swift-ci commented Jul 9, 2018

Comment by Piyush Verma (JIRA)

@vguerra - Yeah, my apologies. It doesn't solve the issue. Also it might tend to ignore any defer at the end of the file. 🙁

@vguerra
Copy link
Contributor

vguerra commented Jul 9, 2018

piverma (JIRA User) no problem at all... thanks for pointing it out anyways 😉.

@jckarter
Copy link
Member Author

jckarter commented Jul 9, 2018

It would probably be better to have the walker track whether you're in a `TopLevelCodeDecl`, by setting a flag while walking the `BraceStmt` of a `TopLevelCodeDecl`, than to try to check other incidental state.

@vguerra
Copy link
Contributor

vguerra commented Jul 9, 2018

thank you @jckarter, will give that a try!

@vguerra
Copy link
Contributor

vguerra commented Jul 15, 2018

hello all!

I just opened a PR for this #17967

@vguerra
Copy link
Contributor

vguerra commented Aug 1, 2018

PR for this issue has been merged

@jckarter
Copy link
Member Author

jckarter commented Aug 1, 2018

Awesome, thanks @vguerra!

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself diagnostics QoI Bug: Diagnostics Quality of Implementation good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

3 participants