Skip to content

[Concurrency] Fix await fix-it placement #37056

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 5 commits into from
Apr 26, 2021

Conversation

etcwilde
Copy link
Member

This PR does some work around the diagnostics on await. The first issue is that the fix-it for await put the await in the wrong place. The issue also exists for the placement of try. The fix-it would put the await immediately before the call or access, but this doesn't work if that point is immediately after a binary operation.

e.g.

let _ = 32 + await asyncFunc()

fails to compile, despite that being where the fix-it suggests putting it.
That gets fixed in this PR. We do this by walking up the parent to an anchor point where we want to emit the await to. By keeping those in a map, we can effectively de-dupe them and map all of the warnings back to a given anchor.
I did run into some difficulties with finding a decent anchor in string interpolated expressions (see CheckEffectsCoverage::walkToAnchor). If we're in a string interpolation, I've found that we make it to the top of the expression and parent becomes a nullptr. The top of the expression for a string interpolation appears to always be a CallExpr, which you then pull out the args from to get a ParenExpr. The subExpr of that appears to be thing thing we're looking for. If this always holds true, we can remove the cascade of if's and just make those the appropriate casts. My concern with being too forceful is getting this assumption wrong and leading to a crashing compiler. As implemented now, it will just suggest that the await goes in a weird spot. e.g.

  print("\(32 + x.number)")
          ^~~~~~~~~~~~~~~
          await

instead of

  print("\(32 + x.number)")
           ^~~~~~~~~~~~~
           await

Additionally, instead of repeatedly emitting errors for every part of the expression, we emit one error with the necessary fix-it, and emit notes for each part that is the cause for needing to put the await in the right place.

e.g.

let _ = 32 + longTask() + longTask() + actorInstance.myNumber + actorInstance.myNumber

will now emit:

awaitPlacement.swift:20:11: error: expression is 'async' but is not marked with 'await'
  let _ = 32 + longTask() + longTask() + actorInstance.myNumber + actorInstance.myNumber
          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          await
awaitPlacement.swift:20:16: note: call is 'async'
  let _ = 32 + longTask() + longTask() + actorInstance.myNumber + actorInstance.myNumber
               ^
awaitPlacement.swift:20:29: note: call is 'async'
  let _ = 32 + longTask() + longTask() + actorInstance.myNumber + actorInstance.myNumber
                            ^
awaitPlacement.swift:20:42: note: property access is 'async'
  let _ = 32 + longTask() + longTask() + actorInstance.myNumber + actorInstance.myNumber
                                         ^
awaitPlacement.swift:20:67: note: property access is 'async'
  let _ = 32 + longTask() + longTask() + actorInstance.myNumber + actorInstance.myNumber
                                                                  ^

Will need to eventually fix the fix-it for try, but given that this is new syntax, it's a bit more important to get right. Folks are probably more familiar with where a try goes by now. Maybe.

Fixes: rdar://76245239

Adding an expression pre-visit to the EffectHandling walker, allowing
sub-classes visit all expressions.
We need to be able to traverse back to the top of expressions in order
to emit the diagnostic to the right place. This patch records the parent
map of the highest expression in the tree that the checker was
constructed with. This will record the mapping for all sub-expressions
as well.
@etcwilde etcwilde requested review from kavon and DougGregor April 24, 2021 05:16
@etcwilde
Copy link
Member Author

@swift-ci please smoke test

This patch restructures how we emit the effect errors for missing
awaits. As we're traversing the expressions, we collect all of the
locations where a missing await is, and metadata about the reason for
the error, as well as the "anchor".

The anchor is where the await should actually be placed in the
expression to fix things. The error is emitted for the whole
sub-expression instead of for the exact synchronization/call-site of the
async function. This avoids the erroneous message for

```
 _ = 32 + asyncFunc()
```

The old behaviour was to put the `await` between the `+` operator and
the call to `asyncFunc()`, but this is not a valid place to put effects.
Note; this issue is also present in the fix-it for `try` insertions and
also needs to be fixed. Instead, the anchor points between the
assignment expression and 32, labelling the whole right-side of the
assignment as being the asynchronous function. We emit notes for each
uncovered async point in the entire expression though, so that someone
can see why the expression is async.
This patch updates all the tests to accept the new error messages.
*Gack* so many things needed cleaning up.
This fixes the placement of the await fix-it inside of interpolated
strings. We were putting the effect between the `\` and `(`, which
doesn't exactly work out very well. I'm intentionally being relatively
gentle with the casting when grabbing the body of the interpolated
string.

I can't think of a valid expression that doesn't result in that
structure, so it's probably safe to do a straight cast, and probably
should switch it over eventually. In the current state-of-affairs, it
will put the fix-it placement in the wrong place if my assumptions are
broken about the structure of the code.
@etcwilde etcwilde force-pushed the ewilde/fix-await-fixits branch from b1c3e0a to e9baa3f Compare April 24, 2021 14:51
@etcwilde
Copy link
Member Author

@swift-ci please smoke test

@etcwilde
Copy link
Member Author

@swift-ci please smoke test macOS

Copy link
Contributor

@ktoso ktoso left a comment

Choose a reason for hiding this comment

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

Nice, looks good to me

@etcwilde
Copy link
Member Author

I'm going to go ahead and merge this because updating the tests is a pain. If we need something changed, I can do that in a separate PR.

@etcwilde etcwilde merged commit d0d9bef into swiftlang:main Apr 26, 2021
@etcwilde etcwilde added the concurrency Feature: umbrella label for concurrency language features label Apr 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
concurrency Feature: umbrella label for concurrency language features
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants