Issue
There appears to be an undocumented feature which allows functions to infer a type from a function in which they are passed as an argument.
I'm trying to understand why this works, and if it's intentional.
Given a Model
, then with a generic update
and apply
function, I can infer a type from apply
when using the result of update
as an argument with the trick T extends infer U ? U : never
:
interface Model {
foo: number
bar: string
}
function apply<T>(fn: (t: T) => T) {};
function whyDoesThisWork() {
function update<T>(u: T extends infer U ? Partial<U> : never) { return (t: T) => ({ ...t, ...u }) };
// Somehow, Typescript is able to infer that we want Model to be the generic supplied for update
apply<Model>(update({ foo: 1 }));
}
function thisDoesntWorkForObviousReasons() {
function update<T>(u: Partial<T>) { return (t: T) => ({ ...t, ...u }) }
// update infers T as { foo: 1 }, because that makes sense
apply<Model>(update({ foo: 1 }));
}
Solution
This behavior occurs because of Contextual Typing.
The "trick" can be extracted to a helper typed named DontInfer
:
type DontInfer<T> = T extends infer S ? S : never;
When DontInfer
is used in a generic function signature, T
will not be inferred where it is used. From my example, the generic argument u
can be said to be of the shape Partial<T>
, but T
will not be inferred by u
:
function update<T>(u: DontInfer<Partial<T>>) { return (t: T) => ({ ...t, ...(u as Partial<T>) }) };
This means that the update
function will not be able to infer T
if it is used on its own:
// T is inferred to be `unknown`
update({ foo: 1 })
But when used in a context, T is able to be inferred:
// T is inferred to be Model
setState<Model>(update({ foo: 1 }));
DontInfer
works for this purpose because it defers evaluating unresolved conditional types.
Answered By - David Shortman
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.