Search code examples
typescriptreadonly

Call Readonly function in Typescript


I'm building an app with an immutable data model and I reached the following dead-end:

I have a function that is stored as immutable like in the following example:

const f: Readonly<() => void> = () => {};
f();

When calling f I get the following error:

This expression is not callable.
  Type 'Readonly<() => void>' has no call signatures. (2349)

I fail to understand what is transformed by Readonly that produces this effect.

Casting back to mutable doesn't seem to be a solution. This produces the same error:

export type Mutable<T> = {
    -readonly [K in keyof T]: T[K];
};

(f as Mutable<typeof f>)();

I'm looking to understand the root cause of this issue, not necessarily look for workarounds, like casting to the exact function type, or not applying Readonly to functions.

Link to playground.


Solution

  • You can use Readonly<Function> to say that the function's properties (e.g. apply, bind, call...) are read-only, and an intersection type to retain the function's call signature.

    type ReadonlyFunction<F extends Function> = Readonly<Function> & F;
    
    const f: ReadonlyFunction<() => void> = () => {};
    
    // OK
    f();
    
    // Error: Expected 0 arguments, but got 2.
    f(1, 2);
    
    // Error: Cannot assign to 'apply' as it is a readonly property.
    f.apply = 'foo';
    

    Playground Link


    The reason why Readonly<() => void> doesn't work seems to be that Readonly is a mapped type, so it maps the properties of the type () => void, but not the call signature.

    It turns out that the type () => void has no properties, so e.g. keyof (() => void) is never, and { [K in keyof (() => void)]: any } is {}.