typescript

Can't constrain function type using guard function


I wrote the following type guard definition:

function is_zero_args<T, F extends (...args: any[]) => T>(func: F): func is () => T {
    return func.length === 0;
}

Logically, this should work - but TypeScript fails the compilation with the following error:

A type predicate's type must be assignable to its parameter's type.
  Type '() => T' is not assignable to type 'F'.
    '() => T' is assignable to the constraint of type 'F', but 'F' could be instantiated with a different subtype of constraint '(...args: any[]) => T'.

I know that F could be created with different args - that's what I'm guarding against.


Solution

  • You don't need/want F to be a type parameter there. Callers are allowed to supply a specific type argument for F to which () => T may not be assignable (e.g. is_zero_args<string, (x: string) => string>(f)). The last line of your error message explains this ("F could be instantiated with a different subtype").

    Here's another example of the same kind of error without involving function types:

    function is_blah<S extends string>(str: S): str is 'blah' {
    //                                                 ^^^^^^
    // A type predicate's type must be assignable to its parameter's type.
    //   Type 'string' is not assignable to type 'S'.
    //     'string' is assignable to the constraint of type 'S', but 'S' could be instantiated with a different subtype of constraint 'string'.
        return str === 'blah';
    }
    

    Usually when a generic function's type parameter only has a single usage in the call signature you can remove that type parameter and instead inline its constraint. Indeed, that works here:

    function is_zero_args<T>(func: (...args: any[]) => T): func is () => T {
        return func.length === 0;
    }