In JavaScript, it's common to have a function that may be called in more than one way – e.g. with handful of positional arguments or a single options object or some combination of the two.
I've been trying to work out how to annotate this.
One way I tried was to annotate rest args as a union of various possible tuples:
type Arguments =
| [string]
| [number]
| [string, number]
;
const foo = (...args: Arguments) => {
let name: string;
let age: number;
// unpack args...
if (args.length > 1) {
name = args[0];
age = args[1];
} else if (typeof args[0] === 'string') {
name = args[0];
age = 0;
} else {
name = 'someone';
age = args[1];
}
console.log(`${name} is ${age}`);
};
// any of these call signatures should be OK:
foo('fred');
foo('fred', 30);
foo(30);
The above snippet is contrived; I could probably just use (...args: Array<string | number>)
in this example, but for more complex signatures (e.g. involving a typed options object that can be alone or with prior args) it would be useful to be able to define a precise, finite set of possible call signatures.
But the above doesn't type-check. You can see a bunch of confusing errors in tryflow.
I also tried typing the function itself as a union of separate entire function defs, but that didn't work either:
type FooFunction =
| (string) => void
| (number) => void
| (string, number) => void
;
const foo: FooFunction = (...args) => {
let name: string;
let age: number;
// unpack args...
if (args.length > 1) {
name = args[0];
age = args[1];
} else if (typeof args[0] === 'string') {
name = args[0];
age = 0;
} else {
name = 'someone';
age = args[1];
}
console.log(`${name} is ${age}`);
};
// any of these call signatures should be OK:
foo('fred');
foo('fred', 30);
foo(30);
How should I approach type-annotating functions with multiple possible call signatures? (Or are multi-signatures considered an anti-pattern in Flow, and I just shouldn't be doing it at all – in which case, what is the recommended approach for interacting with third party libraries that do it?)