Why does this snippet allow Typescript to infer a generic from a "parent" function?

Viewed 106

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.

Playground

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 }));

}
1 Answers

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.

Related