In the following code, the only difference between getDocsWithIdThatWorks
and getDocsWithIdThatComplains
is the callback they are passing to Array.prototype.map
. In one case it's a function expression, and in the other it's an overloaded, generic function. In the first case, TypeScript perfectly understands and agrees about the return type of the overloaded function. But when passing the overloaded function's reference directly as an argument to Array.prototype.map
, TypeScript suddenly infers the return type as DocumentWithId<unknown>
, despite the fact that the array being mapped is clearly typed as QueryDocumentSnapshot<T>
. Why is that so?
interface DocumentSnapshot<T> {
id: string
data(): T | undefined
}
interface QueryDocumentSnapshot<T> extends DocumentSnapshot<T> {
data(): T
}
interface DocumentWithId<T> {
id: string
data?: T
}
interface QueryDocumentWithId<T> {
id: string
data: T
}
function withId<T>(document: QueryDocumentSnapshot<T>): QueryDocumentWithId<T>
function withId<T>(document: DocumentSnapshot<T>): DocumentWithId<T>
function withId<T>(document: DocumentSnapshot<T> | QueryDocumentSnapshot<T>): DocumentWithId<T> | QueryDocumentWithId<T> {
return {
id: document.id,
data: document.data(),
}
}
type Query<T> = () => Promise<QueryDocumentSnapshot<T>[]>
async function getDocsWithIdThatWorks<T>(query: Query<T>): Promise<QueryDocumentWithId<T>[]> {
const result = await query()
return result.map((d) => withId(d))
}
async function getDocsWithIdThatComplains<T>(query: Query<T>): Promise<QueryDocumentWithId<T>[]> {
const result = await query()
return result.map(withId)
}
CodePudding user response:
This is a known design limitation of TypeScript; see microsoft/TypeScript#35501. TypeScript can only properly resolve call signatures for overloaded functions when such functions are actually called.
When you write d => withId(d)
, you are actually calling withId
with a parameter of type QueryDocumentSnapshot<T>
, and the compiler selects the first call signature, as desired.
But when you just pass withId
to map()
, you are not calling withId
and so the proper overload resolution does not happen. Instead the compiler just "gives up" and picks the last signature. And that results in the error you're seeing.
This happens whenever the compiler has to infer a type involving an overloaded function that it does not call. I don't see direct documentation about this for generic type inference, but it is mentioned in the documentation about conditional type inference, where it says:
When inferring from a type with multiple call signatures (such as the type of an overloaded function), inferences are made from the last signature (which, presumably, is the most permissive catch-all case). It is not possible to perform overload resolution based on a list of argument types.