2

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 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

1 Comment

I elaborated on how this functions here: dev.to/davidshortman/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.