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?