Home > Mobile >  Why does applying generics such as Omit<T, K> to a function cause it to not be callable?
Why does applying generics such as Omit<T, K> to a function cause it to not be callable?

Time:07-02

I was playing around with TypeScript and found that applying Omit<T, K> on a callable function, makes it no longer callable:

declare function myCallableFunction(): void;

myCallableFunction(); // valid

type NonOmittedFunction = typeof myCallableFunction;
declare const myNonOmittedFunction: NonOmittedFunction;
myNonOmittedFunction(); // valid

type OmittedFunction = Omit<typeof myCallableFunction, 'foobar'>;
declare const myOmittedFunction: OmittedFunction;
myOmittedFunction(); // This expression is not callable.
                    //  Type 'OmittedFunction' has no call signatures.

Why is this?

Here is a heavily contrived example as to where you may want to do something like this:

declare type CallCountingFunction = (() => void) & { count: number }
const myFunction: CallCountingFunction = (() => { 
                                            const x = () => {}; 
                                            x.count = 0; 
                                            return x;
                                         })()

myFunction.count; // valid
myFunction() // valid
type OmittedFunction = Omit<CallCountingFunction, 'count'>;
declare const myOmittedFunction: OmittedFunction;
myOmittedFunction(); // This expression is not callable.
                    //  Type 'OmittedFunction' has no call signatures.

Playground

This appears to be true for all generic utility types that involve remapping such as Partial<T> and Required<T>.

CodePudding user response:

The utility types aren't magic, they're actually all written in Typescript and we can view them. Omit is defined in terms of Pick and Exclude.

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Nothing crazy yet, but Pick is defined as

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

So Pick<T, K> is always defined to be an object type with indexed keys. There's no call signature here. It's easy to think of Omit as "take my type T and remove some specific things", but internally it really is "create a new object type that has T's keys, minus some things", and since the ability to call a function is not an object key, it doesn't get transferred. The analogy breaks down since functions are, fundamentally, not really designed to be used this way.

  • Related