I tried to call another overloaded function within an overloaded function in typescript. Since the type Func2
is identical to the type Func1
, it is certain that the arguements passed onto func1
from func2
will be typed correctly. However, typescript seems to be unable to pick that up, and throwing an error.
type Func1 = {
(a: string, b: string): void
(a: undefined, b: undefined): void
}
const func1: Func1 = (a, b) => {
console.log(a, b)
}
type Func2 = {
(a: string, b: string): void
(a: undefined, b: undefined): void
}
const func2: Func2 = (a, b) => {
func1(a, b)
console.log(a, b)
}
/*
No overload matches this call.
Overload 1 of 2, '(a: string, b: string): void', gave the following error.
Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
Type 'undefined' is not assignable to type 'string'.
Overload 2 of 2, '(a: undefined, b: undefined): void', gave the following error.
Argument of type 'string | undefined' is not assignable to parameter of type 'undefined'.
Type 'string' is not assignable to type 'undefined'.
*/
How can I resolve this?
EDIT: Here's the actual implementation that I was attempting:
type ParseRandomArgs = {
(a: undefined, b: undefined): [number, number]
(a: number, b: undefined): [number, number]
(a: number, b: number): [number, number]
(a: [number, number], b: undefined): [number, number]
(a: [number], b: undefined): [number, number]
}
type RandomNumber = {
(a: undefined, b: undefined): number
(a: number, b: undefined): number
(a: number, b: number): number
(a: [number, number], b: undefined): number
(a: [number], b: undefined): number
}
const isNullish = (value: any) => value === undefined || value === null
const parseRandomArgs: ParseRandomArgs = (a, b) => {
if (Array.isArray(a)) {
if (a.length === 2) return a
return [0, a[0]]
}
else if (isNullish(b)) return [0, isNullish(a) ? 1 : a as number]
else return [a as number, b as number]
}
const randomFloat: RandomNumber = (a, b) => {
let [min, max] = parseRandomArgs(a, b) // [min, max]
return Math.random() * (max - min) min
}
const randomInt: RandomNumber = (a, b) => {
let [min, max] = parseRandomArgs(a, b) // [min, max]
min = Math.ceil(min)
max = Math.floor(max)
return Math.floor(Math.random() * (max - min) min)
}
CodePudding user response:
As @Mike mentioned, making undefined
arguments optional makes overloading simpler to work out. This should work:
type RandomGenerator = {
(start: number, end?: number): number
(range: [number, number]): number;
(range: [number]): number;
}
const randomFloat: RandomGenerator = (start, end?) => {
const [min, max] = parseRandomArgs(start, end as number | undefined);
return computeRandom(min, max);
}
const randomInt: RandomGenerator = (start, end?) => {
const [min, max] = parseRandomArgs(start, end as number | undefined);
return Math.floor(computeRandom(min, max));
}
const computeRandom = (min: number, max: number): number => {
return Math.random() * (max - min) min;
}
const parseRandomArgs = (first: number | [number, number] | [number], second: number | undefined): [number, number] => {
let args: [number, number];
if (Array.isArray(first) && first.length === 1) {
args = [0, first[0]];
} else if (Array.isArray(first) && first.length === 2) {
args = first;
} else {
args = Number.isFinite(second) ? [first, second as number] : [0, first];
}
const [min, max] = args;
return [Math.ceil(min), Math.floor(max)];
}
CodePudding user response:
I think you try make too clean code
Considering the second example (with a random number): typescript actually join all possible variants of arguments
const randomInt: RandomNumber = (a, b) => {
//a: number | [number, number] | [number] | undefined
//b: number | undefined
}
and your RandomNumber/ParseRandomArgs definitions can be used only for call validation
so the first solution is to extend ParseRandomArgs with the union of all args
type ParseRandomArgs = {
...
(a: [number]| [number, number] | number | undefined, b: number | undefined): [number, number]
}
second, move such union to protected function and convert ParseRandomArgs to a proxy
const _parseRandomArgs = function(a: [number] | [number,number] | number | undefined ,b?: number | undefined): [number,number]{
...
}
const parseRandomArgs: ParseRandomArgs = (a, b) => {
return _parseRandomArgs(a,b);
}
const randomFloat: RandomNumber = (a, b) => {
let [min, max] = _parseRandomArgs(a, b) // [min, max]
return Math.random() * (max - min) min
}
and I suggest you make all undefined arguments - optional
type Fn<
Params extends unknown[] = any[],
Result = any,
> = (...args: Params) => Result;
type CommonParams = [
[max?: number],
[min: number, max?: number],
[minAndMax: [min: number, max: number]],
[maxOnly: [max: number]],
];
type ParseFn = Fn<CommonParams[number], [number, number]>;
type RandomFn = Fn<CommonParams[number], number>;
function isNullish <T>(value: T): value is Exclude<T, NonNullable<T>> {
return value === undefined || value === null;
}
const parseRandomArgs: ParseFn = (...args) => {
const [a, b] = args;
if (Array.isArray(a)) return a.length === 2 ? a : [0, a[0]];
if (isNullish(a)) return [0, 1];
return isNullish(b) ? [0, a] : [a, b];
};
const randomFloat: RandomFn = (...args) => {
const [min, max] = parseRandomArgs(...args);
return Math.random() * (max - min) min;
};
const randomInt: RandomFn = (...args) => {
let [min, max] = parseRandomArgs(...args);
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min) min);
};