Skip to content

[QoI] Improvements to function call & closure diagnostics #7224

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 1 commit into from
Feb 8, 2017

Conversation

jtbandes
Copy link
Contributor

@jtbandes jtbandes commented Feb 3, 2017

@rintaro @jrose-apple This is a follow-up to #7202.

Expands the situations in which we suggest do for erroneous trailing closures, and unused closures. Eliminates some duplicate closure diagnostics.

@jtbandes
Copy link
Contributor Author

jtbandes commented Feb 3, 2017

Hm, as I guess could be expected, most of these failures are due to _ =. Perhaps it's not such a good idea to add this.

if (isa<ClosureExpr>(E)) {
diagnose(E->getLoc(), diag::expression_unused_closure);
diagnose(E->getStartLoc(), diag::brace_stmt_suggest_do)
.fixItInsert(E->getStartLoc(), "do ");
Copy link
Member

Choose a reason for hiding this comment

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

Unless the closure hasAnonymousClosureVars(), it's not likely a do statement, IMO.
hasAnonymousClosureVars() roughly means that the closure does not have explicit in clause.

@@ -44,15 +44,15 @@ func test(a: BadAttributes) -> () { // expected-note * {{did you mean 'test'?}}
//===--- Recovery for braced blocks.

func braceStmt1() {
{ braceStmt1(); } // expected-error {{braced block of statements is an unused closure}} expected-error {{expression resolves to an unused function}}
{ braceStmt1(); } // expected-error {{closure expression is unused}} expected-note {{use 'do' keyword to designate a separate block of statements}} {{3-3=do }}
Copy link
Member

Choose a reason for hiding this comment

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

note message here doesn't make sense to me.
How about "did you mean 'do' statement?" ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was also thinking of using the word scope or scoped somehow.

But I think did you mean to use a 'do' statement? could work... (especially since "do statement" is the terminology used in the documentation)

}

func braceStmt3() {
{ // expected-error {{braced block of statements is an unused closure}}
Copy link
Member

Choose a reason for hiding this comment

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

It's a little unfortunate that we lost this diagnostics.
This is because typeCheckExpression() fails for this single expression closure in StmtChecker::visitBraceStmt().

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, we definitely need some diagnostic here.

Copy link
Contributor Author

@jtbandes jtbandes Feb 3, 2017

Choose a reason for hiding this comment

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

The reason there's no longer a diagnostic here is that there was an error typechecking the closure body. I'm not sure how to convince it to produce additional diagnostics in that case...

if (suggestDiscardResult) {
diagnose(valueE->getLoc(), diag::discard_result_of_call)
.fixItInsert(valueE->getStartLoc(), "_ = ");
}
Copy link
Member

Choose a reason for hiding this comment

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

I recommend to split this fix-it addition into a separate PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please don't defer side effects like this, either. That's a bit too subtle for C++ code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I also considered a fix-it to wrap the call in parentheses, like ({ print("result") }()). What do you think of that option?

It was a bit tricky to get it to happen at the right time, because starting an expression with a closure only seems to be disallowed when it's part of the top-level code (which was a bit surprising to me). But I could probably make it work with some more effort.

@rintaro
Copy link
Member

rintaro commented Feb 3, 2017

Moving diagnosis to Sema makes sense to me. This is to prevent double error:

error: braced block of statements is an unused closure
{
^
error: expression resolves to an unused function
{
^

Also, I like do instead of _ = for closures :)

if (suggestDiscardResult) {
diagnose(valueE->getLoc(), diag::discard_result_of_call)
.fixItInsert(valueE->getStartLoc(), "_ = ");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Please don't defer side effects like this, either. That's a bit too subtle for C++ code.

{ consume($0) }(42) // expected-warning {{braces here form a trailing closure separated from its callee by multiple newlines}} expected-error {{cannot call value of non-function type '()'}} expected-note {{use 'do' keyword to designate a separate block of statements}}
;

// This is technically a valid call, so nothing goes wrong until (42)
Copy link
Contributor

Choose a reason for hiding this comment

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

Ew. Should we forbid trailing closures after closure literals, too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wish, but that'd be a source-breaking change. See the bottom of this function.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure it's a language change that would affect anyone in practice. @DougGregor?

Copy link
Member

Choose a reason for hiding this comment

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

I think it's reasonable to forbid trailing closures after closure literals. Better to beg forgiveness (== allow it only in Swift 3 compatibility mode if it becomes a problem) than keep it in the grammar.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is it intended that closures are allowed to begin an expression that's not at the top level? I think the IIFE pattern is common, but do is better if it's a ->Void closure.

Copy link
Contributor

Choose a reason for hiding this comment

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

We could certainly start parsing that way. The existing warning was intended to keep people from doing it, and to require parentheses if they really wanted to create-and-call all in one go.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, the thing is it's not a warning today. This compiles with no complaints:

func foo() {
    { print("hi") }()
}

It's only an error at the top level, when it gets confused with a trailing closure, or when it produces an unused closure (i.e. isn't called).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm also not sure what the point is of rejecting this at the top level.

}

func braceStmt3() {
{ // expected-error {{braced block of statements is an unused closure}}
Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, we definitely need some diagnostic here.

@jtbandes jtbandes force-pushed the func-y-town branch 3 times, most recently from 226df6a to fbbbacc Compare February 4, 2017 08:47
}

func braceStmt3() {
{ // expected-error {{braced block of statements is an unused closure}}
{
Copy link
Member

@rintaro rintaro Feb 4, 2017

Choose a reason for hiding this comment

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

Why we want diagnostics here is: it's inconsistent with multi statements closure.
Consider:

func foo() {
  // ...
  {
    print("do it.")
    undefinedFunc()
  }
}

In this case, it's diagnosed as closure expression is unused did you mean to use a 'do' statement? despite the error in undefinedFunc().

@jtbandes jtbandes force-pushed the func-y-town branch 2 times, most recently from d8e00a4 to 4f06d4e Compare February 4, 2017 19:10
@jtbandes
Copy link
Contributor Author

jtbandes commented Feb 4, 2017

Trying checkIgnoredExpr regardless of whether typechecking succeeded...

@swift-ci please smoke test

@jtbandes jtbandes force-pushed the func-y-town branch 4 times, most recently from 9cfddfb to 48da5bb Compare February 6, 2017 00:36
@jtbandes
Copy link
Contributor Author

jtbandes commented Feb 6, 2017

@swift-ci please smoke test

// If a closure expression is unused, the user might have intended
// to write "do { ... }".
if (auto *CE = dyn_cast<ClosureExpr>(SubExpr)) {
TC.diagnose(CE->getLoc(), diag::expression_unused_closure);
Copy link
Member

Choose a reason for hiding this comment

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

IMO, we should handle CaptureListExpr here as well.

test.swift:2:3: error: expression resolves to an unused function
  { [x] in
  ^~~~~~~~

closure expression is unused is better I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wow, I didn't realize CaptureListExpr exists as a parent of ClosureExpr. Good idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing this out. I also found that my use of cast<> was crashing because I failed to account for CaptureListExpr.

.fixItInsert(CE->getStartLoc(), "do ");
}
}
else if (isDiscarded && !hadTypeError)
Copy link
Member

Choose a reason for hiding this comment

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

style nitpick } else

@jtbandes jtbandes force-pushed the func-y-town branch 2 times, most recently from a53d306 to 6ebf000 Compare February 7, 2017 07:18
@jtbandes
Copy link
Contributor Author

jtbandes commented Feb 7, 2017

@swift-ci please smoke test

@DougGregor
Copy link
Member

I'm happy with this now, FWIW

@jrose-apple jrose-apple removed their assignment Feb 7, 2017
@jrose-apple
Copy link
Contributor

Abdicating; if @rintaro's also okay with this, go ahead.


// If a closure expression is unused, the user might have intended
// to write "do { ... }".
auto *CE = dyn_cast<ClosureExpr>(SubExpr);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I noticed that I'm not actually checking isDiscarded here. Surprised this didn't break some tests. Will fix this evening.

Copy link
Member

Choose a reason for hiding this comment

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

isDiscarded here means it's in Playground or REPL context or not.
In the current master, "closure as a statement" is diagnosed in Parser regardless of that.
It's considered as a syntactical error. I don't think you need to take it into account.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh good point; if we're already in a BraceStmt and the immediate child is a closure, it must be "unused". I hadn't actually read the isDiscarded definition above.


// If a closure expression is unused, the user might have intended
// to write "do { ... }".
auto *CE = dyn_cast<ClosureExpr>(SubExpr);
Copy link
Member

Choose a reason for hiding this comment

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

isDiscarded here means it's in Playground or REPL context or not.
In the current master, "closure as a statement" is diagnosed in Parser regardless of that.
It's considered as a syntactical error. I don't think you need to take it into account.

@jtbandes jtbandes merged commit c98e515 into swiftlang:master Feb 8, 2017
@jtbandes jtbandes deleted the func-y-town branch February 8, 2017 01:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants