-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Add elaboration & quickfix for async-able arrow function #36342
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 elaboration & quickfix for async-able arrow function #36342
Conversation
src/compiler/checker.ts
Outdated
if ((getFunctionFlags(node) & FunctionFlags.Async) === 0 | ||
// exclude cases where source itself is promisy - this way we don't make a suggestion when relating | ||
// an IPromise and a Promise that are slightly different | ||
&& !getTypeOfPropertyOfType(sourceReturn, "then" as __String) |
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'm surprised we don't have a helper 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.
This meaning...? Seeing if a type is promisish (highly non-technical term)? Yeah, I went looking inside getAwaitedType
to see if we had a helper and saw that all we did was essentially look for a then
property (to match spec behavior, ofc), so that's what I did here. A helper would essentially just curry one argument and cast the result, so it doesn't seem terribly needed.
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 meant checking for a then
property (don't we have a Thenable
concept?).
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.
Nope. Thenable
is just a Promises/A+ spec thing that roughly maps to "object with a then property" (with expected shape and behavior). We have no interface or anything special for that.
src/compiler/checker.ts
Outdated
// exclude cases where source itself is promisy - this way we don't make a suggestion when relating | ||
// an IPromise and a Promise that are slightly different | ||
&& !getTypeOfPropertyOfType(sourceReturn, "then" as __String) | ||
&& checkTypeRelatedTo(createPromiseType(sourceReturn), targetReturn, relation, /*errorNode*/ undefined) |
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 would have guessed it would be cheaper to unwrap targetReturn
, so we wouldn't have to instantiate Promise<source>
.
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.
targetReturn
could be any number of compatible promise-likes which don't unwrap cleanly (like a custom MyStringPromise
). It's much easier to wrap source. (And better mirrors what the runtime does)
errorCodes, | ||
getCodeActions: context => { | ||
const { sourceFile, errorCode, cancellationToken, program, span } = context; | ||
const diagnostic = find(program.getDiagnosticsProducingTypeChecker().getDiagnostics(sourceFile, cancellationToken), getIsMatchingAsyncError(span, errorCode)); |
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.
Can this code be shared with isMissingAwaitError
in addMissingAwait.ts
? It seems like finding a diagnostic with particular related information might be a general utility.
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.
Not quite! In the other quick fix, we're checking if the diagnostic's span matches the input span, then returning the diagnostic if it matches and has the target related span code. In this quick fix, we don't do the filtering on related span at the matching step, because we're looking to find
the span of the related span (so we can use that instead). So, yes, there's a common iteration bit, but to handle the bits variable between the two is essentially just a some
with variable condition, onto which we then append an optional extra filter/map on the final result. Which seems like a bit too multifunctional for a single helper for my tastes.
But it should totally feel similar, since I started writing this quick fix by copying the addMissingAwait
quick fix, which is why the span-matching check is almost identical in structure.
@typescript-bot pack this |
Heya @DanielRosenwasser, I've started to run the tarball bundle task on this PR at 7371ec1. You can monitor the build here. It should now contribute to this PR's status checks. |
Hey @DanielRosenwasser, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running There is also a playground for this build. |
src/compiler/diagnosticMessages.json
Outdated
@@ -5436,6 +5436,14 @@ | |||
"category": "Message", | |||
"code": 95099 | |||
}, | |||
"Add 'async'.": { |
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.
There's already a similar quick fix for this, so the text can be reused.
c: () => "hello", | ||
~~~~~~~ | ||
!!! error TS2322: Type 'string' is not assignable to type 'Promise<string>'. | ||
!!! related TS6502 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:4:8: The expected type comes from the return type of this signature. | ||
!!! related TS1356 tests/cases/compiler/errorOnUnionVsObjectShouldDeeplyDisambiguate.ts:19:10: Did you mean to mark this function as 'async'? |
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 kind of a weird thing now that I think about it - why would I want to mark this as async? There's no await
anywhere there.
If a user did write await
in the function body, we already provide the same error message/quick fix to mark the function as async
, so this could be annoying.
interface Stuff {
b: () => Promise<string>;
}
function foo(): Stuff | Date {
return {
b: _ =>
await "hello" // two error spans with duplicate related spans
}
}
So this is only distinct actionable advice information for a user if they hadn't written await
. Would making this async
actually be the right thing? It does come up, it's unclear if it's the real fix to a problem.
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 kind of a weird thing now that I think about it - why would I want to mark this as async? There's no await anywhere there.
Because async () => "constant"
is way, way more canonical and concise than () => Promise.resolve("constant")
when the API you're using expects a function returning a promise.
So this is only distinct actionable advice information for a user if they hadn't written await
More like, using await
means the containing function should be async to make what you're trying to do work, while if assignability to a promise return fails, it should be async to satisfy the API. Different reasons entirely that happen to have the same fix.
It looks like this just needs a change to re-use an existing error message, plus a merge from master, before it's ready to go. |
Also reuse existing error messages.
@sandersn merged and changed to reuse the existing related span diagnostic messages (though I think it's maybe misleading to reuse the same message for both kinds of the similarly fixed diagnostics, since the "fix all" won't fix all instances of both, just the one). |
@weswigham this is ready to merge, isn't it? |
Yeah, if the messages and such look good to you now. |
Starts to fix part of #35300. The most simple/common case, anyway.