-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Add 'Remove unnecessary await' suggestion and fix #32363
Add 'Remove unnecessary await' suggestion and fix #32363
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.
- Need a test case with parenthesized numbers to make sure dots get treated correctly.
- Definitely say "Remove unnecessary await". It's punchier than a more accurate message.
return; | ||
} | ||
|
||
const parenthesizedExpression = awaitExpression && tryCast(awaitExpression.parent, isParenthesizedExpression); |
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.
what about (await 1).toFixed()
? Does this correctly produce 1..toFixed()
?
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.
Gooood call. It does not 🙈
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.
Is it too embarrassing to leave the parens in and just produce (1).toFixed()
? I mean, most people don't like parens, but they've always treated me well.
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 that’s what I’ll do, when the inner expression is a DecimalIntegerLiteral.
Are you suggesting I change the checker suggestion message, or just affirming my choice for the refactor? |
Affirming your choice for the refactor. |
Other test failures show that |
FWIW I personally find this a bit confusing without thinking about it; when dealing with async code I don’t intuitively think in terms of types - I just go “oh this runs in the background, I should await it”. So my initial reaction to the “no change of type” message is “what on earth are you talking about, of course await wouldn’t change the type” and only 10 seconds later would I go “...oh, Something to think about at least, especially since the |
src/services/utilities.ts
Outdated
@@ -1992,6 +1992,10 @@ namespace ts { | |||
return typeIsAccessible ? res : undefined; | |||
} | |||
|
|||
export function isDecimalIntegerLiteral(node: Node): node is NumericLiteral { | |||
return isNumericLiteral(node) && /^\d+$/.test(node.text); |
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 actually doesn't do what it's supposed to do. NumericLiteral#text
contains the normalized value, e.g. 1.0
-> 1
you probably want to use .getText()
here to get the raw source text of the numeric literal
|
||
const parenthesizedExpression = tryCast(awaitExpression.parent, isParenthesizedExpression); | ||
// (await 0).toFixed() should keep its parens (or add an extra dot for 0..toFixed()) | ||
if (parenthesizedExpression && isPropertyAccessExpression(parenthesizedExpression.parent) && isDecimalIntegerLiteral(awaitExpression.expression)) { |
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 also needs to handle NewExpression
without parens:
(await new C).foo();
// currently fixed to
new C.foo();
// which executes as
new (C.foo)();
// should instead be
(new C).foo()
|
||
const parenthesizedExpression = tryCast(awaitExpression.parent, isParenthesizedExpression); | ||
// (await 0).toFixed() should keep its parens (or add an extra dot for 0..toFixed()) | ||
if (parenthesizedExpression && isPropertyAccessExpression(parenthesizedExpression.parent) && isDecimalIntegerLiteral(awaitExpression.expression)) { |
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.
also needs to handle await
inside NewExpression#expression
:
new (await getC()).Class();
// currently fixed to
new getC().Class();
// which executes as
(new getC()).Class();
// should instead be
new (getC()).Class();
@fatcerberus that’s definitely fair, but for a checker message I wanted to be fairly careful and accurate with the language we use. The only possible alternative I can think of is something like
or
@sandersn @DanielRosenwasser thoughts? |
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.
Two suggestions to consider for now or later.
@@ -27,12 +27,34 @@ namespace ts.codefix { | |||
} | |||
|
|||
const parenthesizedExpression = tryCast(awaitExpression.parent, isParenthesizedExpression); | |||
// (await 0).toFixed() should keep its parens (or add an extra dot for 0..toFixed()) | |||
if (parenthesizedExpression && isPropertyAccessExpression(parenthesizedExpression.parent) && isDecimalIntegerLiteral(awaitExpression.expression)) { | |||
const preserveParens = parenthesizedExpression && ( |
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 still think this is the right thing to do, but now that we have 3 exceptions, the queasy feeling in my stomach says that there more out there. I almost would like to always preserve parentheses, except that the result is so ugly in the common case.
What if we flipped it to only skip parens for identifiers and callexpressions?
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.
Yeah, I had the same feeling. I think this is probably a good plan.
@fatcerberus I disagree, I use types to guide my thinking whenever I write monadic code. I'm not a normal programmer though. Specifically:
Still, I think a type checker reporting an error in terms of types is reasonable. |
@andrewbranch That's fair, but on the other side of that same coin, being too literal with calling out "type" can be confusing too when the existence of a type is an implementation detail, see e.g. discussion starting here w.r.t. excess object literal property errors (an endless source of confusion for newbies): #32158 (comment) In that case the compiler says "type ... is not assignable to" which is literally true (it's a "fresh object literal" type), but ends up looking like a statement about structural typing in general, leading people to believe types are closed. |
Yeah, I like monads too but that's the thing, async/await is specifically designed to hide the existence of the promise monad and let you write basically the same code you would if the monad wasn't there. So re-surfacing the type discrepancy in that context reads as confusing to me. When I decide whether to write an |
I personally like this one:
It seems like a nice compromise: Still in terms of types, but much clearer about what went wrong, so I don't think my strings morphed into numbers or something 😄 |
d0191da
to
f22dc9d
Compare
I don’t like this because
I like this because
@fatcerberus I do very much appreciate the input—keep the suggestions coming in general, but I’m going to stick with the original this time. Thanks all! |
Counterpoint: So is 😄
Point taken on this though, so I'll admit defeat. 👍 |
} | ||
|
||
const parenthesizedExpression = tryCast(awaitExpression.parent, isParenthesizedExpression); | ||
const removeParens = parenthesizedExpression && (isIdentifier(awaitExpression.expression) || isCallExpression(awaitExpression.expression)); |
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.
@andrewbranch it seems you dropped the code necessary to avoid changing the parsing of NewExpression.
However, there's a whole category of exceptions that's not handled here: if the parenthesized AwaitExpression is at the start of the statement, the fixed code could parse as declaration instead of expression:
(await function(){}());
(await class {static fn(){}}.fn());
(await {fn() {}}.fn());
These cases are already handled in factory.ts, see the uses of getLeftmostExpression
Also related to #30646
I hesitated to use the word “unnecessary” since your program could be relying on bizarre side effects of the asynchrony introduced by awaiting something sync’ly available, and indeed the message I wrote for the checker suggestion speaks only of types:
But, it was just too awkward to try to write a code fix message that tries to dance around the issue—so it simply says
Screen capture 🎥