I have encountered an issue while trying to compile a Typescript snippet:
function foo(v: string) { return 'foo'; }
function bar(v: string | number) { return 'bar'; }
const notCallable: typeof foo | typeof bar = function() {} as any;
// Despite both unioned functions accepting string, the type check fails.
notCallable('a');
The compiler determines the type of notCallable
as
((v: string) => string) | ((v: string | number) => string)
, which seems correct but is not callable:
Cannot invoke an expression whose type lacks a call signature. Type '((v: string) => string) | ((v: string | number) => string)' has no compatible call signatures.
If the parameter lists match, it works even with different return types.
function foo(v: string) { return 'foo'; }
function bar(v: string) { return 0; }
const callable: typeof foo | typeof bar = function() {} as any;
// Successfully passes type check because the parameter lists match exactly (even with different return types).
callable('a');
This specific case originated from an attempt to define "continuous numeric D3 scale functions", demonstrated in the example below:
import { ScaleContinuousNumeric, ScaleTime } from 'd3-scale';
type ValidScale = ScaleContinuousNumeric<number, number> | ScaleTime<number, number>;
const s: ValidScale = function() {} as any;
// Unfortunately, only the no-argument overload for `domain` is valid, despite both interfaces having one that accepts `Array<number>`.
s.domain([ 0, 1 ]);
Is there a way to express this without creating a simpler interface that both ScaleContinuousNumeric
and ScaleTime
can be assigned to?