TypeScript Version: [email protected]
Search Terms: TS80007 as of #32384
Code
type MaybePromise<T> = T | Promise<T>
type StatefulModifier<S> = MaybePromise<(arg: S) => number>
type CountActions<S = { count: number }> = Record<string,
(...args: any[]) => StatefulModifier<S>
>
const actions: CountActions = {
// works fine, `state` arg correctly inferred
increment: (toAdd: number) => {
return (state) => state.count + toAdd
},
// works fine, `state` arg correctly inferred from a promise.resolve res
first: () => Promise.resolve(mockedPromise()).then((res) => {
return (state) => state.count + res
}),
// works fine, `state` arg correctly inferred from a new promise
second: () => new Promise((res) => {
res((state) => state.count + 50)
}),
// fails, `state` arg as `any` and TS warning 80007 on async
third: async () => {
const res = await mockedPromise()
// arg type inferred as any
return (arg) => {
return 10 * res
}
},
}
Expected behavior:
The promised function signature of the third object property should be inferred in the same respect as the previous two promised functions.
Actual behavior:
Typescript is able to infer the promised function signature when it's provided through => Promise.resolve or => new Promise(... but unable to do so when supplied as a result of an async function.
However, the inference resolves correctly when the MaybePromise union type is just simply a promise:
type MaybePromise<T> = Promise<T>
Playground Link: Click me
I think we need to update the contextual typing rules for async functions to include the case where the contextual type is a union where one or more constituents is a Promise type
Simplified
type MaybePromise<T> = T | Promise<T>;
type NumberToNumber = (arg: number) => number;
type CountActions = Record<string, () => MaybePromise<NumberToNumber>>;
const actions: CountActions = {
// OK
increment: () => (state) => state,
// OK
first: () => Promise.resolve(mockedPromise()).then((res) =>
(state) => state + res
),
// OK
second: () => new Promise((res) => {
res((state) => state)
}),
// fails, arg is `any`
third: async () => {
return (arg) => {
return 10
}
},
}
const mockedPromise = () => new Promise<number>(res => {
setTimeout(() => res(10), 1200)
})
I have a bug to report, I'll leave this here because it seems related but I'm not sure if it's TypeScript or VSCode responsiblity, since this is just JavaScript in VSCode using JSDocs/ESDocs. But I guess VSCode uses TypeScript to infer types? Please let me know if I should report this in VSCode instead.
x is inferred with any instead of null:
/**
* @returns {Promise<null>}
*/
async method(listener) { }
var x = await method(); // x is 'any' but it should be null
It only fails for null or undefined. It works for other types.
We are hitting this bug in our type definitions for Fastify. See: https://github.com/fastify/fastify/pull/2350
Is there a reason this keeps getting bumped the next version of TS? Anything we can do to help solve it? Is there a recommended workaround?
This issue is fixed in 3.9 judging on both of the playgrounds - @Ethan-Arrowood you might be looking at a different bug
@orta please have a look at this playground I've created. Am I doing something wrong?
~Ahh taking a second look at this issue it seems its more about whats returned from the async functions. But the question still stands for why my playground is not working... should I open a new issue for it?~
I realize my mistake now keep this closed; not a bug with TS
Most helpful comment
I think we need to update the contextual typing rules for async functions to include the case where the contextual type is a union where one or more constituents is a Promise type
Simplified