Search code examples
typescript

Remove the `length` property from a callable type


Assume I have the following type:

export type Foo<T> = (() => T)

Is it possible to remove the length property from it while keep it callable ?

Also, using Omit, Omit<(() => T), 'length'> prevents it from being callable.


Solution

  • You can't really do this. All callables in TypeScript inherit from the Function interface, which is defined in the TypeScript es5.d.ts library to have a numeric length property. Anything you do that results in a callable type will have that length property present.

    The only way to change that would be to fork es5.d.ts locally and remove it, but this would probably break a lot of things and be difficult to maintain. Probably the "correct" approach would be to make a custom linter rule so that use of function length warns you that it might be a mistake. Within TypeScript itself, though, you don't have too many options.

    You can't widen a function to something without length and still have it callable, but you can narrow a function to something whose length is weird/unusable. The standard approach is to narrow to the never type which is a universal subtype and therefore you can always narrow to it:

    type Foo<T> = (() => T) & { length: never };    
    declare const f: Foo<string>;
    f().length.toFixed(); // okay
    f.length.toFixed(); // error
    

    But that won't stop you from checking f.length for truthiness, since even never is allowed there. TypeScript complains about very little when it comes to truthiness checks; indeed the only thing I've found is when you check a function but don't call it. So we can do the crazy things of telling TypeScript that the length property is both number and some function:

    type Foo<T> = (() => T) & { length(x: never): never };
    declare const f: Foo<string>;
    f().length.toFixed(); // okay
    f.length.toFixed(); // error
    if (f.length) { // error but for a crazy reason
        console.log(1)
    }
    

    But that really is a vile lie; the error says that you meant to call f.length and indeed you can sort of do that (although I made it harder by giving it a never parameter):

    f.length(null!) // no error, what?
    

    So I'd say this isn't really the sort of thing TypeScript can do for you well.

    Playground link to code