8
votes

I'm trying to compile this Typescript snippet:

function foo(v: string) { return 'foo'; }
function bar(v: string | number) { return 'bar'; }

const notCallable: typeof foo | typeof bar = function() {} as any;

// Fails type check, even though all the unioned functions accept string.
notCallable('a');

The compiler infers the type of notCallable as ((v: string) => string) | ((v: string | number) => string), which looks fine, but is not considered callable:

Cannot invoke an expression whose type lacks a call signature. Type '((v: string) => string) | ((v: string | number) => string)' has no compatible call signatures.

Note that if the parameter lists match, it works fine, even if the return types differ.

function foo(v: string) { return 'foo'; }
function bar(v: string) { return 0; }

const callable: typeof foo | typeof bar = function() {} as any;

// Passes type check, presumably because the parameter lists match exactly (even though they have different return types).
callable('a');

This example is a simplified case that I originally discovered while trying to describe the notion of "continuous numeric D3 scale functions", which I had tried to define as:

import { ScaleContinuousNumeric, ScaleTime } from 'd3-scale';

type ValidScale = ScaleContinuousNumeric<number, number> | ScaleTime<number, number>;

const s: ValidScale = function() {} as any;

// No good, the only valid overload for `domain` is the no-argument one, even though both interfaces have one that accepts a superset of `Array<number>`.
s.domain([ 0, 1 ]);

Is there a construct that would allow me to express this without having to write a simpler interface that both ScaleContinuousNumeric and ScaleTime are assignable to?

1

1 Answers

3
votes

This is unintended behaviour that appears for versions of TypeScript up til 3.3 where it was fixed. It is also mentioned in the changelog for TypeScript 3.3:

Improved behavior for calling union types

In prior versions of TypeScript, unions of callable types could only be invoked if they had identical parameter lists.

...

In TypeScript 3.3, this is no longer an error.